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