Move 'captivemodid' library to libcaptive.
[captive.git] / src / install / acquire / moduriload.c
1 /* $Id$
2  * W32 disk modules finder for acquiration installation utility
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "moduriload.h" /* self */
23 #include <glib/gmessages.h>
24 #include <mntent.h>
25 #include <glib/ghash.h>
26 #include <glib/glist.h>
27 #include <libgnomevfs/gnome-vfs-uri.h>
28 #include "../libcaptive-install/proc_partitions.h"
29 #include "main.h"
30 #include <string.h>
31 #include "cabinet.h"
32 #include <libgnomevfs/gnome-vfs-ops.h>
33 #include <libgnomevfs/gnome-vfs-directory.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36
37 #include <captive/macros.h>
38 #include <captive/captivemodid.h>
39
40
41 /* Config: */
42 #define MAX_FILE_LOAD_LENGTH 5000000    /* Otherwise use cabextract-over-http. */
43
44
45 /* map: (const xmlChar *)type -> (struct module_available *) */
46 GHashTable *module_available_hash;
47
48 static void module_available_hash_value_destroy_func(struct module_available *module_available)
49 {
50         g_return_if_fail(module_available!=NULL);
51
52         g_free(module_available->file_base);
53         g_free(module_available->uri_text);
54         g_free(module_available);
55 }
56
57 static void module_available_hash_init(void)
58 {
59         if (module_available_hash)
60                 return;
61         module_available_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
62                         (GDestroyNotify)NULL,
63                         (GDestroyNotify)module_available_hash_value_destroy_func);
64 }
65
66
67 void (*acquire_module_available_notify)(struct module_available *module_available);
68 void (*acquire_module_all_modules_found_notify)(void);
69
70 static void mod_uri_load_module_from_memory
71                 (struct captive_captivemodid_module *module,gconstpointer file_base,size_t file_length,GnomeVFSURI *uri)
72 {
73 struct module_available *module_available;
74 gint best_priority;
75 gboolean all_modules_found;
76
77         g_return_if_fail(module!=NULL);
78         g_return_if_fail(file_base!=NULL);
79         g_return_if_fail(uri!=NULL);
80         g_return_if_fail((size_t)module->length==file_length);
81
82         module_available_hash_init();
83         if ((module_available=g_hash_table_lookup(module_available_hash,module->type))) {
84                 if (module_available->module->priority>=module->priority)
85                         return;
86                 }
87
88         captive_new(module_available);
89         module_available->module=module;
90         module_available->file_base=g_memdup(file_base,file_length);
91         module_available->uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
92         /* It may possibly destroy the old 'module_available': */
93         g_hash_table_insert(module_available_hash,(/* de-const */ xmlChar *)module->type,module_available);
94
95         if (!optarg_dry) {
96 const gchar *dest_pathname;
97 int dest_fd;
98
99                 dest_pathname=captive_printf_alloca("%s/%s",G_STRINGIFY(VARLIBCAPTIVEDIR),module->type);
100                 if (-1==(dest_fd=open(dest_pathname,O_CREAT|O_TRUNC|O_WRONLY,0644)))
101                         g_warning(_("Cannot open target file \"%s\": %m"),dest_pathname);
102                 else {
103                         if ((ssize_t)file_length!=write(dest_fd,file_base,file_length))
104                                 g_warning(_("Error writing target file \"%s\": %m"),dest_pathname);
105                         if (close(dest_fd))
106                                 g_warning(_("Error closing target file \"%s\": %m"),dest_pathname);
107                         }
108                 }
109
110         all_modules_found=FALSE;
111
112         best_priority=captive_captivemodid_module_type_best_priority_lookup(module->type);
113         if (best_priority==G_MININT     /* no longer seeking for such module */
114                         || module_available->module->priority==best_priority) {
115                 if (captive_captivemodid_module_type_best_priority_found(module->type)) {
116                         /* Postpone (*acquire_module_all_modules_found_notify)()
117                          * after (*acquire_module_available_notify)().
118                          */
119                         all_modules_found=TRUE;
120                         }
121                 }
122
123         if (acquire_module_available_notify)
124                 (*acquire_module_available_notify)(module_available);
125         if (all_modules_found)
126                 if (acquire_module_all_modules_found_notify)
127                         (*acquire_module_all_modules_found_notify)();
128 }
129
130 void mod_uri_load_file_from_memory(gconstpointer file_base,size_t file_length,GnomeVFSURI *uri)
131 {
132 gchar *file_md5;
133 struct captive_captivemodid_module *module;
134
135         g_return_if_fail(file_base!=NULL);
136         g_return_if_fail(uri!=NULL);
137
138         if ((*ui_progress)(uri))
139                 return;
140
141         file_md5=captive_calc_md5(file_base,file_length);
142         if (!(module=captive_captivemodid_module_md5_lookup(file_md5)))
143                 goto fail_free_file_md5;
144
145         if (strcmp("cabinet",(const char *)module->type))
146                 mod_uri_load_module_from_memory(module,file_base,file_length,uri);
147         else {
148                 struct acquire_cabinet *acquire_cabinet;
149                 /* acquire_cabinet_load() will call mod_uri_load_module_from_memory(): */
150                 acquire_cabinet=acquire_cabinet_new_from_memory(file_base,file_length,uri,module->cabinet_used);
151                 acquire_cabinet_load(acquire_cabinet);
152                 acquire_cabinet_free(acquire_cabinet);
153                 }
154
155 fail_free_file_md5:
156         g_free(file_md5);
157 }
158
159 static void mod_uri_load_file_handle_to_memory(GnomeVFSHandle *handle,GnomeVFSFileInfo *file_info,GnomeVFSURI *uri)
160 {
161 guint8 *file_buffer,file_tail_check;
162 GnomeVFSFileSize bytes_read;
163 GnomeVFSResult errvfsresult;
164
165         g_return_if_fail(handle!=NULL);
166         g_return_if_fail(file_info!=NULL);
167         g_return_if_fail(uri!=NULL);
168
169         /* gnome_vfs_read_entire_file() reads the file by chunks although
170          * it does not need to know the file size.
171          */
172         file_buffer=g_malloc(file_info->size);
173         
174         errvfsresult=gnome_vfs_read(handle,file_buffer,file_info->size,&bytes_read);
175         if (errvfsresult!=GNOME_VFS_OK || bytes_read!=file_info->size)
176                 goto fail_free_file_buffer;
177         /* 'bytes_read' must be !=NULL for GnomeVFS-2.0.x! */
178         errvfsresult=gnome_vfs_read(handle,&file_tail_check,1,&bytes_read);
179         if (!(errvfsresult==GNOME_VFS_ERROR_EOF
180                         /* At least RedHat gnome-vfs2-2.0.2-5
181                          * and ntfsprogs-200309071734-1captive1 and ntfsprogs-gnomevfs-1.0.1-0
182                          * do not report GNOME_VFS_ERROR_EOF.
183                          * FIXME: Check if it is a bug in ntfsprogs-gnomevfs-1.0.1-0.
184                          */
185                         || (errvfsresult==GNOME_VFS_OK && bytes_read==0)))
186                 goto fail_free_file_buffer;
187         mod_uri_load_file_from_memory(file_buffer,file_info->size,uri);
188
189 fail_free_file_buffer:
190         g_free(file_buffer);
191 }
192
193 static void mod_uri_load_file_handle_remote_cabinet
194                 (GnomeVFSHandle **handlep,GnomeVFSFileInfo *file_info,GnomeVFSURI *uri,gint cabinet_used)
195 {
196 struct acquire_cabinet *acquire_cabinet;
197
198         g_return_if_fail(handlep!=NULL);
199         g_return_if_fail(*handlep!=NULL);
200         g_return_if_fail(file_info!=NULL);
201         g_return_if_fail(uri!=NULL);
202
203         acquire_cabinet=acquire_cabinet_new_from_handle(handlep,file_info,uri,cabinet_used);
204         /* acquire_cabinet_load() will call mod_uri_load_module_from_memory(): */
205         acquire_cabinet_load(acquire_cabinet);
206         if (optarg_verbose) {
207 gchar *uri_text;
208
209                 uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
210                 g_message("cabinet_used: %s - %d",uri_text,acquire_cabinet->cabinet_done);
211                 g_free(uri_text);
212                 }
213         acquire_cabinet_free(acquire_cabinet);
214 }
215
216 static void mod_uri_load_file(GnomeVFSURI *uri)
217 {
218 GnomeVFSResult errvfsresult;
219 GnomeVFSFileInfo file_info_local;
220 GnomeVFSHandle *handle;
221
222         g_return_if_fail(uri!=NULL);
223
224         if (GNOME_VFS_OK!=(errvfsresult=gnome_vfs_open_uri(&handle,uri,GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_RANDOM)))
225                 goto fail;
226         CAPTIVE_MEMZERO(&file_info_local);
227         if (GNOME_VFS_OK!=(errvfsresult=gnome_vfs_get_file_info_from_handle(handle,&file_info_local,GNOME_VFS_FILE_INFO_DEFAULT)))
228                 goto fail_close_handle;
229         if (1
230                         && file_info_local.type!=GNOME_VFS_FILE_TYPE_REGULAR
231                         /* FC4 gnome-vfs2-2.10.0-5 "http" returns GNOME_VFS_FILE_TYPE_UNKNOWN
232                          * on the original info query so we need to try directory first.
233                          */
234             && file_info_local.type!=GNOME_VFS_FILE_TYPE_UNKNOWN) {
235                 errvfsresult=GNOME_VFS_ERROR_WRONG_FORMAT;
236                 goto fail_file_info_local_clear;
237                 }
238         if (!(file_info_local.valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
239                 errvfsresult=GNOME_VFS_ERROR_WRONG_FORMAT;
240                 goto fail_file_info_local_clear;
241                 }
242         if (!captive_captivemodid_module_length_is_valid(file_info_local.size)) {
243                 errvfsresult=GNOME_VFS_ERROR_WRONG_FORMAT;
244                 goto fail_file_info_local_clear;
245                 }
246         if (file_info_local.size<=MAX_FILE_LOAD_LENGTH)
247                 mod_uri_load_file_handle_to_memory(handle,&file_info_local,uri);
248         else {
249 gint cabinet_used=captive_captivemodid_cabinet_length_to_used(file_info_local.size);
250
251                 mod_uri_load_file_handle_remote_cabinet(&handle,&file_info_local,uri,cabinet_used);
252                 }
253         errvfsresult=GNOME_VFS_OK;
254         /* PASSTHRU */
255 fail_file_info_local_clear:
256         gnome_vfs_file_info_clear(&file_info_local);
257 fail_close_handle:
258         gnome_vfs_close(handle);
259 fail:;
260 }
261
262 static gboolean mod_uri_load_directory_visit_func
263                 (const gchar *rel_path,GnomeVFSFileInfo *info,gboolean recursing_will_loop,GnomeVFSURI *root_uri /* data */,
264                 gboolean *recurse)
265 {
266         g_return_val_if_fail(rel_path!=NULL,FALSE);
267         g_return_val_if_fail(info!=NULL,FALSE);
268         g_return_val_if_fail(root_uri!=NULL,FALSE);
269         g_return_val_if_fail(recurse!=NULL,FALSE);
270
271         *recurse=FALSE;
272
273         /* Do not: (*ui_progress)(root_uri);
274          * here as we are called with the same 'root_uri' for all of our 'rel_path's.
275          */
276         (*ui_progress)(NULL);
277
278         switch (info->type) {
279                 case GNOME_VFS_FILE_TYPE_REGULAR: {
280 GnomeVFSURI *file_uri;
281
282                         file_uri=gnome_vfs_uri_append_path(root_uri,rel_path);
283                         if ((*ui_progress)(file_uri)) {
284                                 gnome_vfs_uri_unref(file_uri);
285                                 return FALSE;   /* abort traversal */
286                                 }
287                         mod_uri_load_file(file_uri);
288                         gnome_vfs_uri_unref(file_uri);
289                         } break;
290                 case GNOME_VFS_FILE_TYPE_DIRECTORY: {
291 GnomeVFSURI *directory_uri;
292 GnomeVFSDirectoryHandle *directory_handle;
293
294                         /* Never set '*recurse' if it would cause 'Access denied' error
295                          * as it would completely abort the upper gnome_vfs_directory_visit_uri().
296                          * Check the directory accessibility manually:
297                          */
298                         directory_uri=gnome_vfs_uri_append_path(root_uri,rel_path);
299                         if ((*ui_progress)(directory_uri)) {
300                                 gnome_vfs_uri_unref(directory_uri);
301                                 return FALSE;   /* abort traversal */
302                                 }
303                         if (GNOME_VFS_OK==gnome_vfs_directory_open_from_uri(&directory_handle,directory_uri,GNOME_VFS_FILE_INFO_DEFAULT)) {
304                                 *recurse=TRUE;
305                                 gnome_vfs_directory_close(directory_handle);    /* errors ignored */
306                                 }
307                         gnome_vfs_uri_unref(directory_uri);
308                         } break;
309                 default:;
310                 }
311
312         return TRUE;    /* continue traversal */
313 }
314
315 static void mod_uri_load_directory(GnomeVFSURI *uri)
316 {
317 GnomeVFSResult errvfsresult;
318
319         g_return_if_fail(uri!=NULL);
320
321         errvfsresult=gnome_vfs_directory_visit_uri(uri,
322                         GNOME_VFS_FILE_INFO_DEFAULT,    /* info_options */
323                         GNOME_VFS_DIRECTORY_VISIT_SAMEFS,       /* visit_options; 'GNOME_VFS_DIRECTORY_VISIT_LOOPCHECK'? */
324                         (GnomeVFSDirectoryVisitFunc)mod_uri_load_directory_visit_func,
325                         uri);   /* data */
326         /* FC4 gnome-vfs2-2.10.0-5 "http" returns GNOME_VFS_FILE_TYPE_UNKNOWN
327          * on the original info query so we need to try directory first.
328          */
329         if (errvfsresult==GNOME_VFS_ERROR_NOT_A_DIRECTORY) {
330                 mod_uri_load_file(uri);
331                 return;
332                 }
333         if (errvfsresult!=GNOME_VFS_OK) {
334 gchar *uri_text;
335
336                 uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
337                 g_warning(_("Error scanning sub-tree of \"%s\": %s"),uri_text,gnome_vfs_result_to_string(errvfsresult));
338                 g_free(uri_text);
339                 }
340 }
341
342 static void mod_uri_load_internal(GnomeVFSURI *uri,gboolean base_reporting)
343 {
344 GnomeVFSFileInfo file_info_local;
345 GnomeVFSResult errvfsresult;
346
347         g_return_if_fail(uri!=NULL);
348
349         if (optarg_verbose) {
350 gchar *uri_text;
351
352                 uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
353                 g_message(_("Scanning...: %s"),uri_text);
354                 g_free(uri_text);
355                 }
356
357         CAPTIVE_MEMZERO(&file_info_local);
358         file_info_local.type=GNOME_VFS_FILE_TYPE_UNKNOWN;
359         if (GNOME_VFS_OK!=(errvfsresult=gnome_vfs_get_file_info_uri(uri,&file_info_local,GNOME_VFS_FILE_INFO_DEFAULT))) {
360                 if (base_reporting) {
361 gchar *uri_text;
362
363                         uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
364                         g_warning(_("Error loading \"%s\": %s"),uri_text,gnome_vfs_result_to_string(errvfsresult));
365                         g_free(uri_text);
366                         }
367                 return;
368                 }
369         switch (file_info_local.type) {
370                 case GNOME_VFS_FILE_TYPE_REGULAR:   mod_uri_load_file(uri);      break;
371                 case GNOME_VFS_FILE_TYPE_DIRECTORY: mod_uri_load_directory(uri); break;
372                 /* FC4 gnome-vfs2-2.10.0-5 "http" returns GNOME_VFS_FILE_TYPE_UNKNOWN
373                  * on the original info query so we need to try directory first.
374                  */
375                 case GNOME_VFS_FILE_TYPE_UNKNOWN:   mod_uri_load_directory(uri); break;
376                 default:                            /* GNOME_VFS_ERROR_WRONG_FORMAT */;
377                 }
378         gnome_vfs_file_info_clear(&file_info_local);
379 }
380
381 void mod_uri_load(GnomeVFSURI *uri)
382 {
383         g_return_if_fail(uri!=NULL);
384
385         mod_uri_load_internal(uri,
386                         FALSE); /* base_reporting */
387 }
388
389 void mod_uri_load_base_reporting(GnomeVFSURI *uri)
390 {
391         g_return_if_fail(uri!=NULL);
392
393         mod_uri_load_internal(uri,
394                         TRUE);  /* base_reporting */
395 }