/*
 * gedit-vfs-utils.c
 * This file is part of gedit
 *
 * Copyright (C) 2005 - Paolo Borelli and Paolo Maggi
 *
 * 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.
 */

/*
 * Modified by the gedit Team, 2005. See the AUTHORS file for a 
 * list of people on the gedit Team.  
 * See the ChangeLog files for a list of changes. 
 */

#include <string.h>

#include <libgnomevfs/gnome-vfs.h>

#include "gedit-vfs-utils.h"
#include "gedit-utils.h"
#include "gedit-document.h"

static gboolean
uri_may_be_symlink (GnomeVFSURI *uri)
{
	GnomeVFSFileInfo *info;
	GnomeVFSResult vfs_res;
	gboolean ret = TRUE; /* default to TRUE in case of doubt */

	info = gnome_vfs_file_info_new ();
	vfs_res = gnome_vfs_get_file_info_uri (uri,
					       info,
					       GNOME_VFS_FILE_INFO_DEFAULT);
	if (vfs_res == GNOME_VFS_OK)
	{
		if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) &&
		    (info->type != GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK))
		{
			ret = FALSE;
		}
	}

	gnome_vfs_file_info_unref (info);

	return ret;
}

/* does this really work for non local dirs?
 * I fear that it returns false with most of the vfs methods...
 * gnome-vfs devs are slackers! ;-)
 */
static gboolean
dir_is_writable (GnomeVFSURI *uri)
{
	gchar *dir;
	GnomeVFSFileInfo *info;
	GnomeVFSResult vfs_res;
	gboolean ret = FALSE;

	dir = gnome_vfs_uri_extract_dirname (uri);
	if (dir == NULL)
		return ret;

	info = gnome_vfs_file_info_new ();
	vfs_res = gnome_vfs_get_file_info (dir,
					   info,
					   GNOME_VFS_FILE_INFO_DEFAULT |
					   GNOME_VFS_FILE_INFO_FOLLOW_LINKS |
					   GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS);
	if (vfs_res == GNOME_VFS_OK)
	{
		ret = ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_ACCESS) &&
		       (info->permissions & GNOME_VFS_PERM_ACCESS_WRITABLE));
	}

	gnome_vfs_file_info_unref (info);
	g_free (dir);

	return ret;
}

static GnomeVFSResult
write_to_file (GnomeVFSHandle *handle, gchar *data, GnomeVFSFileSize len)
{
	GnomeVFSFileSize written;
	GnomeVFSResult res = GNOME_VFS_OK;

	while (len > 0)
	{
		res = gnome_vfs_write (handle, data, len, &written);

		len -= written;
		data += written;

		if (res != GNOME_VFS_OK)
			break;
	}

	return res;
}

static gboolean
uri_exists (const gchar *uri)
{
	gboolean ret;
	GnomeVFSURI *vfs_uri;

	vfs_uri = gnome_vfs_uri_new (uri);
	if (vfs_uri == NULL)
		return TRUE; /* safer for our goals... */

	ret = gnome_vfs_uri_exists (vfs_uri);

	gnome_vfs_uri_unref (vfs_uri);

	return ret;
}

/**
 * Returs the URI for the backup file
 *
 * the URI is just the concatenation of orig_uri and bak_ext, unless
 * we are not going to keep the backup: in that case we do not overwrite
 * the file.
 */
static gchar *
get_backup_file_uri (const gchar *orig_uri,
		     const gchar *bak_extension,
		     gboolean     keep_backup)
{
	gchar *bak_uri;
	gchar *bak_ext = NULL;

	if ((bak_ext == NULL) || (strlen (bak_ext) < 1))
		bak_ext = g_strdup ("~");
	else
		bak_ext = g_strdup (bak_extension);

	bak_uri = g_strconcat (orig_uri, bak_ext, NULL);

	g_free (bak_ext);

	/* If we are not going to keep the backup file and bak_uri
	 * already exists, try to use another name.
	 * Change one character, just before the extension.
	 */
	if (!keep_backup && uri_exists (bak_uri))
	{
		gchar *wp;

		wp = bak_uri + strlen (bak_uri) - 1 - strlen (bak_ext);
		g_return_val_if_fail (wp > bak_uri, NULL);

		*wp = 'z';
		while ((*wp > 'a') && uri_exists (bak_uri))
			--*wp;

		/* They all exist??? Must be something wrong. */
		if (*wp == 'a')
		{
			g_free (bak_uri);
			bak_uri = NULL;
		}
	}

	return bak_uri;
}

/**
 * vfs_mkstemp:
 * @handle: A pointer to a pointer to a GnomeVFSHandle object
 * @tmpl: Template filename
 *
 * Opens a temporary file. See the mkstemp() documentation
 * on most UNIX-like systems.  
 * 
 * The parameter is a string that should match the rules for
 * mkstemp(), i.e. end in "XXXXXX". The X string will 
 * be modified to form the name of a file that didn't exist.
 *
 * Returns an integer representing the result of the operation.
 * Handle will contain a GnomeVFSHandle to the file
 * opened for reading and writing. The file handle should be
 * closed with gnome_vfs_close(). The permissions for the newly created file
 * are 0600.
 */
static GnomeVFSResult
vfs_mkstemp (GnomeVFSHandle **handle, gchar *tmpl)
{
	gint len;
	gchar *XXXXXX;
	gint count;
	static const gchar letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	static const gint NLETTERS = sizeof (letters) - 1;
	glong value;
	GTimeVal tv;
	static gint counter = 0;

	len = strlen (tmpl);
	if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
		return GNOME_VFS_ERROR_GENERIC; /* FIXME pick a better one */

	/* This is where the Xs start.  */
	XXXXXX = &tmpl[len - 6];

	/* Get some more or less random data.  */
	g_get_current_time (&tv);
	value = (tv.tv_usec ^ tv.tv_sec) + counter++;

	for (count = 0; count < 100; value += 7777, ++count)
	{
		GnomeVFSResult  res;

		glong v = value;

		/* Fill in the random bits.  */
		XXXXXX[0] = letters[v % NLETTERS];
		v /= NLETTERS;
		XXXXXX[1] = letters[v % NLETTERS];
		v /= NLETTERS;
		XXXXXX[2] = letters[v % NLETTERS];
		v /= NLETTERS;
		XXXXXX[3] = letters[v % NLETTERS];
		v /= NLETTERS;
		XXXXXX[4] = letters[v % NLETTERS];
		v /= NLETTERS;
		XXXXXX[5] = letters[v % NLETTERS];

		res = gnome_vfs_create (handle,
					tmpl,
					GNOME_VFS_OPEN_READ | GNOME_VFS_OPEN_WRITE,
					TRUE,
					0600);

		if (res == GNOME_VFS_OK)
			return res;

		/* Any other error will apply also to other names we might
		 * try, and there are 2^32 or so of them, so give up now.
	 	 */
		if (res != GNOME_VFS_ERROR_FILE_EXISTS)
			return res;
	}

	/* We got out of the loop because we ran out of combinations to try.  */
	return GNOME_VFS_ERROR_GENERIC;
}

/* sets the permissions on @uri to those of @info, but it's a bit more
 * paranoid: if set GID fails reset group perms to the one specified for Others
 */
static GnomeVFSResult
set_permissions (const gchar *uri, GnomeVFSFileInfo *info)
{
	GnomeVFSResult res, res2;
	GnomeVFSFileInfo *dest_info;

	if (!info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS)
		return GNOME_VFS_ERROR_BAD_PARAMETERS;

	res = gnome_vfs_set_file_info (uri,
				       info, 
				       GNOME_VFS_SET_FILE_INFO_PERMISSIONS |
				       GNOME_VFS_SET_FILE_INFO_OWNER);

	/* be paranaoid and read the info we just tried to write */
	dest_info = gnome_vfs_file_info_new ();
	res2 = gnome_vfs_get_file_info (uri,
					dest_info,
					GNOME_VFS_FILE_INFO_DEFAULT);
	if (res2 == GNOME_VFS_OK)
	{
		/*
		 * If setting the group failed try to set the protection
		 * bits for the group same as the protection bits for
		 * others.
		 */
		if (info->gid != dest_info->gid)
		{
			dest_info->permissions = (info->permissions & 0707) |
						  ((info->permissions & 07) << 3);

			gnome_vfs_set_file_info (uri,
			     			 dest_info,
			     			 GNOME_VFS_SET_FILE_INFO_PERMISSIONS);
		}
	}

	gnome_vfs_file_info_unref (dest_info);

	return res;
}

static GnomeVFSResult
copy_file_data (GnomeVFSHandle *source_handle,
		GnomeVFSHandle *target_handle,
		guint block_size)
{
	GnomeVFSResult res;
	gpointer buffer;
	const gchar *write_buffer;

	buffer = g_malloc (block_size);

	do
	{
		GnomeVFSFileSize bytes_read;
		GnomeVFSFileSize bytes_to_write;
		GnomeVFSFileSize bytes_written;

		res = gnome_vfs_read (source_handle,
				      buffer,
				      block_size,
				      &bytes_read);

		if (res != GNOME_VFS_OK || bytes_read == 0)
		{
			break;
		}

		bytes_to_write = bytes_read;

		write_buffer = buffer;
		do 
		{
			res = gnome_vfs_write (target_handle, 
					       write_buffer,
					       bytes_to_write,
					       &bytes_written);

			bytes_to_write -= bytes_written;
			write_buffer += bytes_written;
		} 
		while ((res == GNOME_VFS_OK) && (bytes_to_write > 0));

	} while (res == GNOME_VFS_OK);

	if (res == GNOME_VFS_ERROR_EOF)
	{
		res = GNOME_VFS_OK;
	}

	g_free (buffer);

	return res;
}

/* cut down version of gnome-vfs-xfer, but we need to follow
 * symlinks on the source (to properly backup a -> b), but not follow
 * symlinks on the destination (so if we have a~ -> /etc/passwd, a~
 * must be moved away and rewritten instead of overwriting /etc/passwd!)
 */
static GnomeVFSResult	
copy_file (const gchar *source, const gchar *dest)
{
	GnomeVFSResult res;
	GnomeVFSFileInfo *info = NULL;
	GnomeVFSHandle *source_handle = NULL;
	GnomeVFSHandle *dest_handle = NULL;
	GnomeVFSURI *source_uri = NULL;
	GnomeVFSURI *dest_uri = NULL;
	guint io_block_size;

	g_return_val_if_fail (source != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
	g_return_val_if_fail (dest != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);	

	source_uri = gnome_vfs_uri_new (source);
	dest_uri = gnome_vfs_uri_new (dest);
	if (source_uri == NULL || dest_uri == NULL)
	{
		res = GNOME_VFS_ERROR_INVALID_URI;
		goto out;
	}

	/* FIXME: We assume that dest and source are on the same file_system (add a check) -
	 * Paolo (Jan 13, 2004)
	 */
	//pbor: why? should work across FS...

	/* FIXME: We assume that destination and/or destination dir is writable - Paolo 
	 * (Jan 13, 2004) */
	//pbor: looks like a reasonable assumption, not a fixme

	info = gnome_vfs_file_info_new ();
	res = gnome_vfs_get_file_info_uri (source_uri,
					   info,
					   GNOME_VFS_FILE_INFO_DEFAULT |
					   GNOME_VFS_FILE_INFO_FOLLOW_LINKS);

	if (res != GNOME_VFS_OK)
	{
		goto out;
	}

	/* Open source */
	res = gnome_vfs_open_uri (&source_handle, source_uri, GNOME_VFS_OPEN_READ);
	if (res != GNOME_VFS_OK)
	{
		goto out;
	}

	/* Remove old dest file, if present */
	res = gnome_vfs_unlink_from_uri (dest_uri);
	if ((res != GNOME_VFS_OK) && (res != GNOME_VFS_ERROR_NOT_FOUND))
	{
		goto out;
	}

	/* Create dest: create in exclusive way to avoid the file being created 
	 * while we were sleeping (symlink hacker attack?) */
	res = gnome_vfs_create_uri (&dest_handle, 
				    dest_uri, 
				    GNOME_VFS_OPEN_WRITE,
				    TRUE /* exclusive */, 
				    0666);
	if (res != GNOME_VFS_OK)
	{
		goto out;
	}

	if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_IO_BLOCK_SIZE) && 
	    (info->io_block_size > 0))
	{
		io_block_size = info->io_block_size;
	}
	else
	{
		io_block_size = 8192;
	}

	res = copy_file_data (source_handle, dest_handle, io_block_size);
	if (res != GNOME_VFS_OK)
	{
		goto out;
	}

	/* Ignore errors for closing read file */
	res = gnome_vfs_close (source_handle);
	if (res == GNOME_VFS_OK)
	{
		source_handle = NULL;
	}

	res = gnome_vfs_close (dest_handle);
	if (res != GNOME_VFS_OK)
	{
		goto out;
	}
	dest_handle = NULL;

	/* Ignore errors while setting file info attributes at this point */
	if (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS) 
	{
		gint perm;

		perm = info->permissions;

		/* Set file protection same as original file, but
		 * strip s-bit */
		info->permissions = perm & 0777;

		set_permissions (dest, info);
	}

	/* Call this last so nothing else changes the times */
	gnome_vfs_set_file_info_uri (dest_uri, info, GNOME_VFS_SET_FILE_INFO_TIME);

 out:
	if (info != NULL)
		gnome_vfs_file_info_unref (info);
	if (dest_uri != NULL)
		gnome_vfs_uri_unref (dest_uri);
	if (source_uri != NULL)
		gnome_vfs_uri_unref (source_uri);
	if (source_handle != NULL)
		gnome_vfs_close (source_handle);
	if (dest_handle != NULL)
		gnome_vfs_close (dest_handle);

	return res;
}

#if 0
/* we whitelist the methods we trust */
gboolean
vfs_method_whitelist (const gchar *uri)
{
	
}
#endif

gboolean
gedit_vfs_save_file (const gchar *uri,
		     gchar	 *data,
		     gboolean	  create_backup,
		     gchar	 *bak_extension,
		     GError     **error)
{
	gboolean success = FALSE;

	GnomeVFSURI      *vfs_uri;
	GnomeVFSFileInfo *info = NULL;
	GnomeVFSResult    vfs_res;
	gboolean          is_link = FALSE;
	mode_t            saved_umask;
	guint             perms = 0;
	gchar            *bak_uri = NULL;

	g_return_val_if_fail ((uri != NULL) && (strlen (uri) > 0), FALSE);
	g_return_val_if_fail (data != NULL, FALSE);

//OK bisogna solo includere qualcosa perche' compili
/*	if (strlen (uri) > GEDIT_MAX_PATH_LEN)
	{
		g_set_error (error,
			     GEDIT_DOCUMENT_IO_ERROR,
			     GNOME_VFS_ERROR_NAME_TOO_LONG,
			     gnome_vfs_result_to_string (GNOME_VFS_ERROR_NAME_TOO_LONG));

		return FALSE;
	}
*/
	vfs_uri = gnome_vfs_uri_new (uri);
	if (vfs_uri == NULL)
	{
		g_set_error (error, 
			     GEDIT_DOCUMENT_IO_ERROR,
			     GNOME_VFS_ERROR_INVALID_URI,
			     gnome_vfs_result_to_string (GNOME_VFS_ERROR_INVALID_URI));

		return FALSE;
	}

	// FIXME: check if vfs-method allows writing

	// FIXME: disable concatenated uris for now

	// Set overwriting ??? Per ora no

	/* init default permissions */
	// paolo: there are problems with the sticky bit and with S_ISGID
	// pbor: uhm... which?
	saved_umask = umask(0);
	perms = (GNOME_VFS_PERM_USER_READ   |
		 GNOME_VFS_PERM_USER_WRITE  |
		 GNOME_VFS_PERM_GROUP_READ  |
		 GNOME_VFS_PERM_GROUP_WRITE |
		 GNOME_VFS_PERM_OTHER_READ  |
		 GNOME_VFS_PERM_OTHER_WRITE) &
		~saved_umask;
	umask (saved_umask);

	// Creazione buffer ??? Per ora no

	/* Get information about original file (if there is one) _following_ symlinks. */
	info = gnome_vfs_file_info_new ();
	vfs_res = gnome_vfs_get_file_info_uri (vfs_uri,
					       info,
					       GNOME_VFS_FILE_INFO_DEFAULT |
					       GNOME_VFS_FILE_INFO_FOLLOW_LINKS |
					       GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS);

	if (vfs_res == GNOME_VFS_ERROR_NOT_FOUND) 
	{
		/* We are creating a new file, just write and we're done */

		GnomeVFSHandle *handle;

		vfs_res = gnome_vfs_create (&handle,
					    uri,
					    GNOME_VFS_OPEN_WRITE,
					    FALSE,
					    perms);
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR, 
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			goto out;
		}

		vfs_res = write_to_file (handle, data, strlen (data));
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			gnome_vfs_close (handle);

			goto out;
		}

		gnome_vfs_close (handle);
		success = TRUE;

		goto out;
	}
	else if (vfs_res == GNOME_VFS_OK)
	{
		/* The file already exists */

		/* Check if is regular file
		 * If the type is not available be conservative and error out.
		 */
		if (!(info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_TYPE) ||
		    !(info->type == GNOME_VFS_FILE_TYPE_REGULAR))
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     GNOME_VFS_ERROR_GENERIC,
				     gnome_vfs_result_to_string (GNOME_VFS_ERROR_GENERIC));

			goto out;
		}

		/* Check if the file is writable.
		 * If access is not a valid info field (e.g. with ssh), assume
		 * that it's writable and try to go on.
		 *
		 * !!!!!!!!!!!!!!!!!NOTA turn off before commit and special case ssh (and others?)
		 */
		if ((info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_ACCESS) &&
		    !(info->permissions & GNOME_VFS_PERM_ACCESS_WRITABLE))
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     GNOME_VFS_ERROR_READ_ONLY,
				     gnome_vfs_result_to_string (GNOME_VFS_ERROR_READ_ONLY));

			goto out;
		}

		/* TODO: Check mtime, to see if the file changed since last read. */

		/* Check if it's a hard link, defaulting to yes */
		if (!(info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_LINK_COUNT) ||
		    (info->link_count > 1))
		{
			is_link = TRUE;
		}

		/* if not a hard link, check if is a symlink */
		if (!is_link)
			is_link = uri_may_be_symlink (vfs_uri);
	}
	else
	{
		/* there were other errors getting info of old file: error out */

		g_set_error (error,
			     GEDIT_DOCUMENT_IO_ERROR,
			     vfs_res,
			     gnome_vfs_result_to_string (vfs_res));

		goto out;
	}

	/* If the dir isn't writable we can only truncate and overwrite without
	 * making backups. We do that only if force_overwrite is specified
	 * otherwise we return a special error and the user has the chance to
	 * confirm if he really wants to overwrite. */
	/* FIXME: for now we just error out. */
	/* FIXME2: is this necessary/reliable? Can't we just try to create the
	 *         tmp file and handle the exception?
	 */
	if (!dir_is_writable (vfs_uri))
	{
		g_set_error (error,
			     GEDIT_DOCUMENT_IO_ERROR,
			     GNOME_VFS_ERROR_ACCESS_DENIED,
			     gnome_vfs_result_to_string (GNOME_VFS_ERROR_ACCESS_DENIED));

		goto out;
	}

	bak_uri = get_backup_file_uri (uri, bak_extension, create_backup);

	/* If the vfs method allows "move", the file is not a symlink
	 * and we are able to restore permissions, use the following
	 * faster strategy: save to a  temp file, rename the original
	 * file to backup and rename the temp file to the real file name.
	 */
	/* FIXME: we only allow this for local files since move is not 
	 * reliable on others vfs methods
	 */
	if (gedit_utils_uri_has_file_scheme (uri) && !is_link && 0)
	{
		gchar *tmp_uri;
		GnomeVFSHandle *handle;

		/* FIXME: is this ok for concatenated uris? */
		tmp_uri = g_strconcat (uri, ".gedit-save-XXXXXX", NULL);

		vfs_res = vfs_mkstemp (&handle, tmp_uri);
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			g_free (tmp_uri);
			goto out;
		}

		/* try to restore permissions */
		vfs_res = set_permissions (tmp_uri, info);
		if (vfs_res == GNOME_VFS_OK)
		{
			/* write to the tmp file */
			vfs_res = write_to_file (handle, data, strlen (data));
			if (vfs_res != GNOME_VFS_OK)
			{
				g_set_error (error,
					     GEDIT_DOCUMENT_IO_ERROR,
					     vfs_res,
					     gnome_vfs_result_to_string (vfs_res));

				gnome_vfs_close (handle);
				gnome_vfs_unlink (tmp_uri);
				g_free (tmp_uri);

				goto out;
			}

			if (create_backup)
			{
				vfs_res = gnome_vfs_move (uri, bak_uri, TRUE);
				if (vfs_res != GNOME_VFS_OK)
				{
					g_set_error (error,
						     GEDIT_DOCUMENT_IO_ERROR,
						     vfs_res,
						     gnome_vfs_result_to_string (vfs_res));

					gnome_vfs_close (handle);
					gnome_vfs_unlink (tmp_uri); //no error checking... what can we anyway?
					g_free (tmp_uri);

					goto out;
				}
			}

			/* finally move the tmp file back to uri */
			vfs_res = gnome_vfs_move (tmp_uri, uri, TRUE);
			if (vfs_res != GNOME_VFS_OK)
			{
				g_set_error (error,
					     GEDIT_DOCUMENT_IO_ERROR,
					     vfs_res,
					     gnome_vfs_result_to_string (vfs_res));

				gnome_vfs_close (handle);

				/* try to restore the backup file and remove the tmp file */
				if (create_backup)
				{
					/* without force_replace and without error checking...
					 * this is just a blind try */
					gnome_vfs_move (bak_uri, uri, FALSE);
					gnome_vfs_unlink (tmp_uri);
				}

				g_free (tmp_uri);

				goto out;
			}

			gnome_vfs_close (handle);
			g_free (tmp_uri);

			success = TRUE;
		}
		else
		{
			/* failed to set perms, fall back to the slower strategy
			 * so that permission are preserved by truncate().
			 */
			g_print ("could not restore perms\n");

			gnome_vfs_close (handle);
			gnome_vfs_unlink (tmp_uri);
			g_free (tmp_uri);
		}
	}

	/* If the first strategy didn't work fallback to the following:
	 * copy the file to the backup file and then truncate and overwrite
	 * the file with the new contents.
	 */
	if (!success)
	{
		GnomeVFSHandle *handle;

		vfs_res = copy_file (uri, bak_uri);
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			goto out;
		}

		/* we open for writing with the TRUNCATE flag so that it works
		 * for links and permissions are preserved... however this is not
		 * implemented in all the vfs methods. If we can' truncate and the
		 * file is not a link, try to remove it and create a new one.
		 */
		vfs_res = gnome_vfs_open_uri (&handle,
					      vfs_uri,
					      GNOME_VFS_OPEN_WRITE |
					      GNOME_VFS_OPEN_TRUNCATE);
		if ((vfs_res == GNOME_VFS_ERROR_INVALID_OPEN_MODE) &&
		    !is_link)
		{
			gnome_vfs_unlink_from_uri (vfs_uri);
			vfs_res = gnome_vfs_create_uri (&handle,
							vfs_uri,
							GNOME_VFS_OPEN_WRITE,
							FALSE,
							info->permissions);
		}
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR, 
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			goto out;
		}

		vfs_res = write_to_file (handle, data, strlen (data));
		if (vfs_res != GNOME_VFS_OK)
		{
			g_set_error (error,
				     GEDIT_DOCUMENT_IO_ERROR,
				     vfs_res,
				     gnome_vfs_result_to_string (vfs_res));

			gnome_vfs_close (handle);

			/* blind try to copy the bak file back */
			copy_file (bak_uri, uri);

			goto out;
		}

		gnome_vfs_close (handle);

		/* file was written succesfully, remove the backup if 
		 * we don't want to keep it */
		if (!create_backup)
			gnome_vfs_unlink (bak_uri);

		success = TRUE;
	}

 out:
	if (vfs_uri)
		gnome_vfs_uri_unref (vfs_uri);
	if (info)
		gnome_vfs_file_info_unref (info);

	g_free (bak_uri);

	return success;
}

