/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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. */ #include "config.h" #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include "libgimpbase/gimpbase.h" #include "libgimpconfig/gimpconfig.h" #ifdef G_OS_WIN32 #include #include "libgimpbase/gimpwin32-io.h" #endif #include "base-types.h" #ifndef _O_BINARY #define _O_BINARY 0 #endif #ifndef _O_TEMPORARY #define _O_TEMPORARY 0 #endif #include "base-utils.h" #include "tile.h" #include "tile-rowhints.h" #include "tile-swap.h" #include "tile-private.h" #include "gimp-intl.h" typedef enum { SWAP_IN = 1, SWAP_OUT, SWAP_DELETE } SwapCommand; typedef gint (* SwapFunc) (gint fd, Tile *tile, SwapCommand cmd); #define MAX_OPEN_SWAP_FILES 16 typedef struct _SwapFile SwapFile; typedef struct _SwapFileGap SwapFileGap; struct _SwapFile { gchar *filename; gint fd; GList *gaps; gint64 swap_file_end; gint64 cur_position; }; struct _SwapFileGap { gint64 start; gint64 end; }; static void tile_swap_command (Tile *tile, gint command); static void tile_swap_default_in (SwapFile *swap_file, Tile *tile); static void tile_swap_default_out (SwapFile *swap_file, Tile *tile); static void tile_swap_default_delete (SwapFile *swap_file, Tile *tile); static gint64 tile_swap_find_offset (SwapFile *swap_file, gint64 bytes); static void tile_swap_open (SwapFile *swap_file); static void tile_swap_resize (SwapFile *swap_file, gint64 new_size); static SwapFileGap * tile_swap_gap_new (gint64 start, gint64 end); static void tile_swap_gap_destroy (SwapFileGap *gap); static SwapFile * gimp_swap_file = NULL; static const gint64 swap_file_grow = 1024 * TILE_WIDTH * TILE_HEIGHT * 4; static gboolean seek_err_msg = TRUE; static gboolean read_err_msg = TRUE; static gboolean write_err_msg = TRUE; #ifdef G_OS_WIN32 #define LARGE_SEEK(f, o, w) _lseeki64 (f, o, w) #define LARGE_TRUNCATE(f, s) win32_large_truncate (f, s) static gint win32_large_truncate (gint fd, gint64 size) { if (LARGE_SEEK (fd, size, SEEK_SET) == size && SetEndOfFile ((HANDLE) _get_osfhandle (fd))) return 0; else return -1; } #else #define LARGE_SEEK(f, o, t) lseek (f, o, t) #define LARGE_TRUNCATE(f, s) ftruncate (f, s) #endif #ifdef GIMP_UNSTABLE static void tile_swap_print_gaps (SwapFile *swap_file) { GList *list; for (list = swap_file->gaps; list; list = list->next) { SwapFileGap *gap = list->data; g_print (" %"G_GINT64_FORMAT" - %"G_GINT64_FORMAT"\n", gap->start, gap->end); } } #endif void tile_swap_init (const gchar *path) { gchar *basename; gchar *dirname; g_return_if_fail (gimp_swap_file == NULL); g_return_if_fail (path != NULL); dirname = gimp_config_path_expand (path, TRUE, NULL); basename = g_strdup_printf ("gimpswap.%lu", (unsigned long) get_pid ()); /* create the swap directory if it doesn't exist */ if (! g_file_test (dirname, G_FILE_TEST_EXISTS)) g_mkdir_with_parents (dirname, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); gimp_swap_file = g_slice_new (SwapFile); gimp_swap_file->filename = g_build_filename (dirname, basename, NULL); gimp_swap_file->gaps = NULL; gimp_swap_file->swap_file_end = 0; gimp_swap_file->cur_position = 0; gimp_swap_file->fd = -1; g_free (basename); g_free (dirname); } void tile_swap_exit (void) { #ifdef HINTS_SANITY extern int tile_exist_peak; g_printerr ("Tile exist peak was %d Tile structs (%d bytes)", tile_exist_peak, tile_exist_peak * sizeof(Tile)); #endif if (tile_global_refcount () != 0) g_warning ("tile ref count balance: %d\n", tile_global_refcount ()); g_return_if_fail (gimp_swap_file != NULL); #ifdef GIMP_UNSTABLE if (gimp_swap_file->swap_file_end != 0) { g_warning ("swap file not empty: \"%s\"\n", gimp_filename_to_utf8 (gimp_swap_file->filename)); tile_swap_print_gaps (gimp_swap_file); } #endif #ifdef G_OS_WIN32 /* should close before unlink */ if (gimp_swap_file->fd > 0) { close (gimp_swap_file->fd); gimp_swap_file->fd = -1; } #endif g_unlink (gimp_swap_file->filename); g_free (gimp_swap_file->filename); g_slice_free (SwapFile, gimp_swap_file); gimp_swap_file = NULL; } /* check if we can open a swap file */ gboolean tile_swap_test (void) { g_return_val_if_fail (gimp_swap_file != NULL, FALSE); /* make sure this duplicates the open() call from tile_swap_open() */ gimp_swap_file->fd = g_open (gimp_swap_file->filename, O_CREAT | O_RDWR | _O_BINARY | _O_TEMPORARY, S_IRUSR | S_IWUSR); if (gimp_swap_file->fd != -1) { close (gimp_swap_file->fd); gimp_swap_file->fd = -1; g_unlink (gimp_swap_file->filename); return TRUE; } return FALSE; } void tile_swap_in (Tile *tile) { if (tile->swap_offset == -1) { tile_alloc (tile); return; } tile_swap_command (tile, SWAP_IN); } void tile_swap_out (Tile *tile) { tile_swap_command (tile, SWAP_OUT); } void tile_swap_delete (Tile *tile) { tile_swap_command (tile, SWAP_DELETE); } static void tile_swap_command (Tile *tile, gint command) { if (gimp_swap_file->fd == -1) { tile_swap_open (gimp_swap_file); if (G_UNLIKELY (gimp_swap_file->fd == -1)) return; } switch (command) { case SWAP_IN: tile_swap_default_in (gimp_swap_file, tile); break; case SWAP_OUT: tile_swap_default_out (gimp_swap_file, tile); break; case SWAP_DELETE: tile_swap_default_delete (gimp_swap_file, tile); break; } } /* The actual swap file code. The swap file consists of tiles * which have been moved out to disk in order to conserve memory. * The swap file format is free form. Any tile in memory may * end up anywhere on disk. * An actual tile in the swap file consists only of the tile data. * The offset of the tile on disk is stored in the tile data structure * in memory. */ static void tile_swap_default_in (SwapFile *swap_file, Tile *tile) { gint nleft; gint64 offset; if (tile->data) return; if (swap_file->cur_position != tile->swap_offset) { swap_file->cur_position = tile->swap_offset; offset = LARGE_SEEK (swap_file->fd, tile->swap_offset, SEEK_SET); if (offset == -1) { if (seek_err_msg) g_message ("unable to seek to tile location on disk: %s", g_strerror (errno)); seek_err_msg = FALSE; return; } } tile_alloc (tile); nleft = tile->size; while (nleft > 0) { gint err; do { err = read (swap_file->fd, tile->data + tile->size - nleft, nleft); } while ((err == -1) && ((errno == EAGAIN) || (errno == EINTR))); if (err <= 0) { if (read_err_msg) g_message ("unable to read tile data from disk: " "%s (%d/%d bytes read)", g_strerror (errno), err, nleft); read_err_msg = FALSE; return; } nleft -= err; } swap_file->cur_position += tile->size; /* Do not delete the swap from the file */ /* tile_swap_default_delete (swap_file, fd, tile); */ read_err_msg = seek_err_msg = TRUE; } static void tile_swap_default_out (SwapFile *swap_file, Tile *tile) { gint bytes; gint nleft; gint64 offset; gint64 newpos; bytes = TILE_WIDTH * TILE_HEIGHT * tile->bpp; /* If there is already a valid swap_offset, use it */ if (tile->swap_offset == -1) newpos = tile_swap_find_offset (swap_file, bytes); else newpos = tile->swap_offset; if (swap_file->cur_position != newpos) { offset = LARGE_SEEK (swap_file->fd, newpos, SEEK_SET); if (offset == -1) { if (seek_err_msg) g_message ("unable to seek to tile location on disk: %s", g_strerror (errno)); seek_err_msg = FALSE; return; } swap_file->cur_position = newpos; } nleft = tile->size; while (nleft > 0) { gint err = write (swap_file->fd, tile->data + tile->size - nleft, nleft); if (err <= 0) { if (write_err_msg) g_message ("unable to write tile data to disk: " "%s (%d/%d bytes written)", g_strerror (errno), err, nleft); write_err_msg = FALSE; return; } nleft -= err; } swap_file->cur_position += tile->size; /* Do NOT free tile->data because we may be pre-swapping. * tile->data is freed in tile_cache_zorch_next */ tile->dirty = FALSE; tile->swap_offset = newpos; write_err_msg = seek_err_msg = TRUE; } static void tile_swap_default_delete (SwapFile *swap_file, Tile *tile) { SwapFileGap *gap; SwapFileGap *gap2; GList *tmp; GList *tmp2; gint64 start; gint64 end; if (tile->swap_offset == -1) return; start = tile->swap_offset; end = start + TILE_WIDTH * TILE_HEIGHT * tile->bpp; tile->swap_offset = -1; tmp = swap_file->gaps; while (tmp) { gap = tmp->data; if (end == gap->start) { gap->start = start; if (tmp->prev) { gap2 = tmp->prev->data; if (gap->start == gap2->end) { gap2->end = gap->end; tile_swap_gap_destroy (gap); swap_file->gaps = g_list_remove_link (swap_file->gaps, tmp); g_list_free (tmp); } } break; } else if (start == gap->end) { gap->end = end; if (tmp->next) { gap2 = tmp->next->data; if (gap->end == gap2->start) { gap2->start = gap->start; tile_swap_gap_destroy (gap); swap_file->gaps = g_list_remove_link (swap_file->gaps, tmp); g_list_free (tmp); } } break; } else if (end < gap->start) { gap = tile_swap_gap_new (start, end); tmp2 = g_list_alloc (); tmp2->data = gap; tmp2->next = tmp; tmp2->prev = tmp->prev; if (tmp->prev) tmp->prev->next = tmp2; tmp->prev = tmp2; if (tmp == swap_file->gaps) swap_file->gaps = tmp2; break; } else if (!tmp->next) { gap = tile_swap_gap_new (start, end); tmp->next = g_list_alloc (); tmp->next->data = gap; tmp->next->prev = tmp; break; } tmp = tmp->next; } if (!swap_file->gaps) { gap = tile_swap_gap_new (start, end); swap_file->gaps = g_list_append (swap_file->gaps, gap); } tmp = g_list_last (swap_file->gaps); gap = tmp->data; if (gap->end == swap_file->swap_file_end) { tile_swap_resize (swap_file, gap->start); tile_swap_gap_destroy (gap); swap_file->gaps = g_list_remove_link (swap_file->gaps, tmp); g_list_free (tmp); } } static void tile_swap_open (SwapFile *swap_file) { g_return_if_fail (swap_file->fd == -1); /* duplicate this open() call in tile_swap_test() */ swap_file->fd = g_open (swap_file->filename, O_CREAT | O_RDWR | _O_BINARY | _O_TEMPORARY, S_IRUSR | S_IWUSR); if (swap_file->fd == -1) g_message (_("Unable to open swap file. GIMP has run out of memory " "and cannot use the swap file. Some parts of your images " "may be corrupted. Try to save your work using different " "filenames, restart GIMP and check the location of the " "swap directory in your Preferences.")); } static void tile_swap_resize (SwapFile *swap_file, gint64 new_size) { if (swap_file->swap_file_end > new_size) { if (LARGE_TRUNCATE (swap_file->fd, new_size) != 0) { g_message (_("Failed to resize swap file: %s"), g_strerror (errno)); return; } } swap_file->swap_file_end = new_size; } static gint64 tile_swap_find_offset (SwapFile *swap_file, gint64 bytes) { SwapFileGap *gap; GList *tmp; gint64 offset; tmp = swap_file->gaps; while (tmp) { gap = tmp->data; if ((gap->end - gap->start) >= bytes) { offset = gap->start; gap->start += bytes; if (gap->start == gap->end) { tile_swap_gap_destroy (gap); swap_file->gaps = g_list_remove_link (swap_file->gaps, tmp); g_list_free (tmp); } return offset; } tmp = tmp->next; } offset = swap_file->swap_file_end; tile_swap_resize (swap_file, swap_file->swap_file_end + swap_file_grow); if ((offset + bytes) < (swap_file->swap_file_end)) { gap = tile_swap_gap_new (offset + bytes, swap_file->swap_file_end); swap_file->gaps = g_list_append (swap_file->gaps, gap); } return offset; } static SwapFileGap * tile_swap_gap_new (gint64 start, gint64 end) { SwapFileGap *gap = g_slice_new (SwapFileGap); gap->start = start; gap->end = end; return gap; } static void tile_swap_gap_destroy (SwapFileGap *gap) { g_slice_free (SwapFileGap, gap); }