1 /**
2 * pqiv
3 *
4 * Copyright (c) 2013-2014, Phillip Berndt
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18 #include "filebuffer.h"
19 #include <errno.h>
20 #include <string.h>
21 #include <glib/gstdio.h>
22 #include <gio/gio.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26
27 #ifdef _POSIX_VERSION
28 #define HAS_MMAP
29 #endif
30
31 #ifdef HAS_MMAP
32 #include <sys/mman.h>
33 #endif
34
35 struct buffered_file {
36 GBytes *data;
37 char *file_name;
38 int ref_count;
39 gboolean file_name_is_temporary;
40 };
41
42 GHashTable *file_buffer_table = NULL;
43 GRecMutex file_buffer_table_mutex;
44
45 #ifdef HAS_MMAP
46 extern GFile *gfile_for_commandline_arg(const char *);
47
48 struct buffered_file_mmap_info {
49 void *ptr;
50 int fd;
51 size_t size;
52 };
53
buffered_file_mmap_free_helper(struct buffered_file_mmap_info * info)54 static void buffered_file_mmap_free_helper(struct buffered_file_mmap_info *info) {
55 munmap(info->ptr, info->size);
56 close(info->fd);
57 g_slice_free(struct buffered_file_mmap_info, info);
58 }
59 #endif
60
buffered_file_as_bytes(file_t * file,GInputStream * data,GError ** error_pointer)61 GBytes *buffered_file_as_bytes(file_t *file, GInputStream *data, GError **error_pointer) {
62 g_rec_mutex_lock(&file_buffer_table_mutex);
63 if(!file_buffer_table) {
64 file_buffer_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
65 }
66 struct buffered_file *buffer = g_hash_table_lookup(file_buffer_table, file->file_name);
67 if(!buffer) {
68 GBytes *data_bytes = NULL;
69
70 if((file->file_flags & FILE_FLAGS_MEMORY_IMAGE)) {
71 if(file->file_data_loader) {
72 data_bytes = file->file_data_loader(file, error_pointer);
73 }
74 else {
75 data_bytes = g_bytes_ref(file->file_data);
76 }
77
78 if(!data_bytes) {
79 g_rec_mutex_unlock(&file_buffer_table_mutex);
80 return NULL;
81 }
82 }
83 else {
84
85 #ifdef HAS_MMAP
86 // If this is a local file, try to mmap() it first instead of loading it completely
87 GFile *input_file = gfile_for_commandline_arg(file->file_name);
88 char *input_file_abspath = g_file_get_path(input_file);
89 if(input_file_abspath) {
90 GFileInfo *file_info = g_file_query_info(input_file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, error_pointer);
91 if(!file_info) {
92 g_object_unref(input_file);
93 g_rec_mutex_unlock(&file_buffer_table_mutex);
94 return NULL;
95 }
96 goffset input_file_size = g_file_info_get_size(file_info);
97 g_object_unref(file_info);
98
99 int fd = open(input_file_abspath, O_RDONLY);
100 g_free(input_file_abspath);
101 if(fd < 0) {
102 g_object_unref(input_file);
103 g_rec_mutex_unlock(&file_buffer_table_mutex);
104 *error_pointer = g_error_new(g_quark_from_static_string("pqiv-filebuffer-error"), 1, "Opening the file failed with errno=%d: %s", errno, strerror(errno));
105 return NULL;
106 }
107 void *input_file_data = mmap(NULL, input_file_size, PROT_READ, MAP_SHARED, fd, 0);
108
109 if(input_file_data != MAP_FAILED) {
110 struct buffered_file_mmap_info *mmap_info = g_slice_new(struct buffered_file_mmap_info);
111 mmap_info->ptr = input_file_data;
112 mmap_info->fd = fd;
113 mmap_info->size = input_file_size;
114
115 data_bytes = g_bytes_new_with_free_func(input_file_data, input_file_size, (GDestroyNotify)buffered_file_mmap_free_helper, mmap_info);
116 }
117 else {
118 close(fd);
119 }
120 }
121 g_object_unref(input_file);
122 #endif
123
124 if(data_bytes) {
125 // mmap() above worked
126 }
127 else if(!data) {
128 data = image_loader_stream_file(file, error_pointer);
129 if(!data) {
130 g_rec_mutex_unlock(&file_buffer_table_mutex);
131 return NULL;
132 }
133 data_bytes = g_input_stream_read_completely(data, image_loader_cancellable, error_pointer);
134 g_object_unref(data);
135 }
136 else {
137 data_bytes = g_input_stream_read_completely(data, image_loader_cancellable, error_pointer);
138 }
139
140 if(!data_bytes) {
141 g_rec_mutex_unlock(&file_buffer_table_mutex);
142 return NULL;
143 }
144 }
145 buffer = g_new0(struct buffered_file, 1);
146 g_hash_table_insert(file_buffer_table, g_strdup(file->file_name), buffer);
147 buffer->data = data_bytes;
148 }
149 buffer->ref_count++;
150 g_rec_mutex_unlock(&file_buffer_table_mutex);
151 return buffer->data;
152 }
153
buffered_file_as_local_file(file_t * file,GInputStream * data,GError ** error_pointer)154 char *buffered_file_as_local_file(file_t *file, GInputStream *data, GError **error_pointer) {
155 g_rec_mutex_lock(&file_buffer_table_mutex);
156 if(!file_buffer_table) {
157 file_buffer_table = g_hash_table_new(g_str_hash, g_str_equal);
158 }
159 struct buffered_file *buffer = g_hash_table_lookup(file_buffer_table, file->file_name);
160 if(buffer) {
161 buffer->ref_count++;
162 g_rec_mutex_unlock(&file_buffer_table_mutex);
163 return buffer->file_name;
164 }
165
166 buffer = g_new0(struct buffered_file, 1);
167 g_hash_table_insert(file_buffer_table, g_strdup(file->file_name), buffer);
168
169 gchar *path = NULL;
170 if(!(file->file_flags & FILE_FLAGS_MEMORY_IMAGE)) {
171 GFile *input_file = g_file_new_for_commandline_arg(file->file_name);
172 path = g_file_get_path(input_file);
173 g_object_unref(input_file);
174 }
175 if(path) {
176 buffer->file_name = path;
177 buffer->file_name_is_temporary = FALSE;
178 }
179 else {
180 gboolean local_data = FALSE;
181 if(!data) {
182 data = image_loader_stream_file(file, error_pointer);
183 if(!data) {
184 g_hash_table_remove(file_buffer_table, file->file_name);
185 g_rec_mutex_unlock(&file_buffer_table_mutex);
186 return NULL;
187 }
188 local_data = TRUE;
189 }
190
191 GFile *temporary_file;
192 GFileIOStream *iostream = NULL;
193 gchar *extension = strrchr(file->file_name, '.');
194 if(extension) {
195 gchar *name_template = g_strdup_printf("pqiv-XXXXXX%s", extension);
196 temporary_file = g_file_new_tmp(name_template, &iostream, error_pointer);
197 g_free(name_template);
198 }
199 else {
200 temporary_file = g_file_new_tmp("pqiv-XXXXXX.ps", &iostream, error_pointer);
201 }
202 if(!temporary_file) {
203 g_printerr("Failed to buffer %s: Could not create a temporary file in %s\n", file->file_name, g_get_tmp_dir());
204 if(local_data) {
205 g_object_unref(data);
206 }
207 g_hash_table_remove(file_buffer_table, file->file_name);
208 g_rec_mutex_unlock(&file_buffer_table_mutex);
209 return NULL;
210 }
211
212 if(g_output_stream_splice(g_io_stream_get_output_stream(G_IO_STREAM(iostream)), data, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, image_loader_cancellable, error_pointer) < 0) {
213 g_hash_table_remove(file_buffer_table, file->file_name);
214 if(local_data) {
215 g_object_unref(data);
216 }
217 g_rec_mutex_unlock(&file_buffer_table_mutex);
218 return NULL;
219 }
220
221 buffer->file_name = g_file_get_path(temporary_file);
222 buffer->file_name_is_temporary = TRUE;
223
224 g_object_unref(iostream);
225 g_object_unref(temporary_file);
226 if(local_data) {
227 g_object_unref(data);
228 }
229 }
230
231 buffer->ref_count++;
232 g_rec_mutex_unlock(&file_buffer_table_mutex);
233 return buffer->file_name;
234 }
235
buffered_file_unref(file_t * file)236 void buffered_file_unref(file_t *file) {
237 g_rec_mutex_lock(&file_buffer_table_mutex);
238 struct buffered_file *buffer = g_hash_table_lookup(file_buffer_table, file->file_name);
239 if(!buffer) {
240 g_rec_mutex_unlock(&file_buffer_table_mutex);
241 return;
242 }
243 if(--buffer->ref_count == 0) {
244 if(buffer->data) {
245 g_bytes_unref(buffer->data);
246 }
247 if(buffer->file_name) {
248 if(buffer->file_name_is_temporary) {
249 g_unlink(buffer->file_name);
250 }
251 g_free(buffer->file_name);
252 }
253 g_hash_table_remove(file_buffer_table, file->file_name);
254 }
255 g_rec_mutex_unlock(&file_buffer_table_mutex);
256 }
257