Use regular system GnomeVFS 'http' method if it already supports seek().
[captive.git] / src / install / acquire / cabinet.c
1 /* $Id$
2  * cabextract interface 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 "cabinet.h"    /* self */
23 #include <glib/gmessages.h>
24 #include <libgnomevfs/gnome-vfs-file-size.h>
25 #include <libgnomevfs/gnome-vfs-ops.h>
26 #include "cabextract/cabextract.h"
27 #include "captivemodid.h"
28 #include "moduriload.h"
29 #include <sys/mman.h>
30 #include <unistd.h>
31 #include "main.h"
32 #include <signal.h>
33 #include <setjmp.h>
34 #include <sys/time.h>
35
36 #include <captive/macros.h>
37
38
39 /* Config: */
40 #define ACQUIRE_CABINET_READ_RAW_READ_TRY_MAX 5
41 #define ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT 20
42 #define ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC 0
43 #define ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC 100000
44
45
46 #define ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT_ITERS \
47                 ((ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT *1000000LL) \
48                 /(ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC*1000000LL+ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC))
49
50
51 void acquire_cabinet_seek(struct acquire_cabinet *acquire_cabinet,GnomeVFSFileOffset offset)
52 {
53         g_return_if_fail(acquire_cabinet!=NULL);
54
55         /* Do not: (*ui_progress)(acquire_cabinet->uri);
56          * as we currently extract some specific file out of it.
57          */
58         (*ui_progress)(NULL);
59
60         acquire_cabinet->offset=offset;
61 }
62
63 void acquire_cabinet_seek_skip(struct acquire_cabinet *acquire_cabinet,GnomeVFSFileOffset offset)
64 {
65         g_return_if_fail(acquire_cabinet!=NULL);
66
67         (*ui_progress)(NULL);
68
69         acquire_cabinet->offset+=offset;
70 }
71
72 GnomeVFSFileOffset acquire_cabinet_tell(struct acquire_cabinet *acquire_cabinet)
73 {
74         g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
75
76         (*ui_progress)(NULL);
77
78         return acquire_cabinet->offset;
79 }
80
81 static guint handler_SIGALRM_hits;
82 static sigjmp_buf handler_SIGALRM_sigjmp_buf;
83
84 static void handler_SIGALRM(int signo)
85 {
86         g_return_if_fail(signo==SIGALRM);
87
88         /* Try to abort the read(2) call first.
89          * If it already read something it will return the partially read data.
90          * Otherwise gnome_vfs_inet_connection_read() will loop back to retry read(2)
91          * and we will abort it after 1 second. OK, some data may be read that time
92          * but who cares.
93          */
94         if (handler_SIGALRM_hits++<ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT_ITERS
95                         && !(*ui_progress)(NULL))
96                 return;
97
98         siglongjmp(handler_SIGALRM_sigjmp_buf,1);       /* 1; meaning: !=0 */
99 }
100
101 /* FIXME: This is hack.
102  * Correct way would be to use 'GnomeVFSCancellation'
103  * to abort 'GnomeVFSInetConnection' acting as 'GnomeVFSSocket'.
104  * This abort should be handled from 'http'/'httpcaptive' handler
105  * but gnome_vfs_cancellation_cancel() cannot be invoked from
106  * the asynchronous slave thread.
107  */
108 static GnomeVFSResult acquire_cabinet_read_raw
109                 (struct acquire_cabinet *acquire_cabinet,gpointer buffer,GnomeVFSFileSize bytes,GnomeVFSFileSize *bytes_read,
110                 GnomeVFSFileOffset offset)
111 {
112 gint try=0;
113
114         g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
115         g_return_val_if_fail(buffer!=NULL || bytes==0,GNOME_VFS_ERROR_BAD_PARAMETERS);
116         g_return_val_if_fail(bytes_read!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
117
118         *bytes_read=0;
119
120         if (!bytes)
121                 return GNOME_VFS_ERROR_EOF;
122
123         while (try++<=ACQUIRE_CABINET_READ_RAW_READ_TRY_MAX) {
124 GnomeVFSResult errvfsresult;
125 GnomeVFSHandle *handle_new;
126 struct sigaction oldact;
127 int errint;
128 struct itimerval itimerval;
129
130                 if ((*ui_progress)(NULL))
131                         return GNOME_VFS_ERROR_INTERRUPTED;
132
133                 if (!sigsetjmp(
134                                 handler_SIGALRM_sigjmp_buf,     /* env */
135                                 TRUE)) {        /* savesigs */
136                         handler_SIGALRM_hits=0;
137                         errint=sigaction(
138                                         SIGALRM,        /* signum */
139                                         NULL,   /* act */
140                                         &oldact);       /* oldact */
141                         g_assert(errint==0);
142                         signal(SIGALRM,handler_SIGALRM);
143                         itimerval.it_interval.tv_sec=ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC;
144                         itimerval.it_interval.tv_usec=ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC;
145                         itimerval.it_value=itimerval.it_interval;
146                         errint=setitimer(
147                                         ITIMER_REAL,    /* which */
148                                         &itimerval,     /* value */
149                                         NULL);  /* ovalue */
150                         g_assert(errint==0);
151                         errvfsresult=gnome_vfs_seek(*acquire_cabinet->handlep,GNOME_VFS_SEEK_START,offset);
152                         if (GNOME_VFS_OK==errvfsresult)
153                                 errvfsresult=gnome_vfs_read(*acquire_cabinet->handlep,buffer,bytes,bytes_read);
154                         }
155                 else
156                         errvfsresult=GNOME_VFS_ERROR_INTERRUPTED;
157                 itimerval.it_interval.tv_sec=0;
158                 itimerval.it_interval.tv_usec=0;
159                 itimerval.it_value=itimerval.it_interval;
160                 errint=setitimer(
161                                 ITIMER_REAL,    /* which */
162                                 &itimerval,     /* value */
163                                 NULL);  /* ovalue */
164                 g_assert(errint==0);
165                 errint=sigaction(
166                                 SIGALRM,        /* signum */
167                                 &oldact,        /* act */
168                                 NULL);  /* oldact */
169                 g_assert(errint==0);
170                 if (errvfsresult==GNOME_VFS_OK) {
171                         g_assert(*bytes_read>0);
172                         return GNOME_VFS_OK;
173                         }
174
175                 /* Reopen '*acquire_cabinet->handlep' */
176                 g_assert(acquire_cabinet->handle_uri!=NULL);
177                 if (GNOME_VFS_OK==(errvfsresult=gnome_vfs_open_uri(&handle_new,acquire_cabinet->handle_uri,GNOME_VFS_OPEN_READ))) {
178                         gnome_vfs_close(*acquire_cabinet->handlep);     /* errors ignored */
179                         *acquire_cabinet->handlep=handle_new;
180                         }
181                 }
182
183         return GNOME_VFS_ERROR_IO;
184 }
185
186 #define ACQUIRE_CABINET_BYTE_CACHED(acquire_cabinet,pos)     (!(acquire_cabinet)->base_cached \
187                                                            || (acquire_cabinet)->base_cached[(pos)/8] &  1<<((pos)&7))
188 #define ACQUIRE_CABINET_SET_BYTE_CACHED(acquire_cabinet,pos) ((acquire_cabinet)->base_cached[(pos)/8] |= 1<<((pos)&7))
189
190 GnomeVFSResult acquire_cabinet_read
191                 (struct acquire_cabinet *acquire_cabinet,gpointer buffer,GnomeVFSFileSize bytes,GnomeVFSFileSize *bytes_read)
192 {
193 GnomeVFSFileOffset offset_start,offset_end,read_behind;
194 GnomeVFSResult errvfsresult;
195 GnomeVFSFileSize bytes_read_now;
196
197         g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
198         g_return_val_if_fail(buffer!=NULL || bytes==0,GNOME_VFS_ERROR_BAD_PARAMETERS);
199
200         *bytes_read=0;
201
202         if ((*ui_progress)(NULL))
203                 return GNOME_VFS_ERROR_INTERRUPTED;
204
205         bytes=MAX(0,MIN(bytes,acquire_cabinet->size-acquire_cabinet->offset));
206         if (!bytes)
207                 return GNOME_VFS_ERROR_EOF;
208
209         while (bytes) {
210                 read_behind =acquire_cabinet->offset+bytes;
211
212                 /* GnomeVFS block transfer: */
213                 offset_start=acquire_cabinet->offset;
214                 offset_end  =acquire_cabinet->offset;
215                 while (offset_end<read_behind && !ACQUIRE_CABINET_BYTE_CACHED(acquire_cabinet,offset_end))
216                         offset_end++;
217                 if (offset_end>offset_start) {
218                         errvfsresult=acquire_cabinet_read_raw(acquire_cabinet,
219                                         acquire_cabinet->base+offset_start,offset_end-offset_start,&bytes_read_now,offset_start);
220                         if (errvfsresult!=GNOME_VFS_OK)
221                                 return errvfsresult;
222                         g_assert(bytes_read_now>0);
223                         acquire_cabinet->cabinet_done+=bytes_read_now;
224                         if (ui_progress_bar)
225                                 (*ui_progress_bar)(acquire_cabinet->cabinet_done,acquire_cabinet->cabinet_used);
226                         while (bytes_read_now) {
227                                 ACQUIRE_CABINET_SET_BYTE_CACHED(acquire_cabinet,offset_start);
228                                 offset_start++;
229                                 bytes_read_now--;
230                                 }
231                         }
232
233                 /* Memory block transfer: */
234                 offset_start=acquire_cabinet->offset;
235                 offset_end  =acquire_cabinet->offset;
236                 while (offset_end<read_behind && ACQUIRE_CABINET_BYTE_CACHED(acquire_cabinet,offset_end))
237                         offset_end++;
238                 memcpy(buffer,acquire_cabinet->base+offset_start,offset_end-offset_start);
239                 if (bytes_read)
240                         *bytes_read+=offset_end-offset_start;
241                 buffer+=offset_end-offset_start;
242                 bytes-=offset_end-offset_start;
243                 acquire_cabinet->offset=offset_end;
244                 }
245
246         return GNOME_VFS_OK;
247 }
248
249 static void acquire_cabinet_set_uri(struct acquire_cabinet *acquire_cabinet,GnomeVFSURI *uri)
250 {
251 GnomeVFSURI *uri_cabextract;
252
253         g_return_if_fail(acquire_cabinet!=NULL);
254         g_return_if_fail(uri!=NULL);
255
256         /* FIXME: HACK: Use proper 'cabextract' scheme after it gets implemented.
257          * GnomeVFS will return NULL on gnome_vfs_uri_new() with scheme not available.
258          */
259         uri_cabextract=gnome_vfs_uri_new("file://");
260         g_assert(uri_cabextract->parent==NULL);
261         /* Do not: g_assert(!strcmp(uri_cabextract->method_string,"file"));
262          *         uri_cabextract->method_string=g_strdup("cabextract");
263          * as it will just strip such anchor. FIXME: Why?
264          */
265
266         uri_cabextract->parent=gnome_vfs_uri_dup(uri);
267
268         acquire_cabinet->uri=uri_cabextract;
269         acquire_cabinet->handle_uri=gnome_vfs_uri_ref(uri);
270         acquire_cabinet->filename=gnome_vfs_uri_to_string(acquire_cabinet->uri,GNOME_VFS_URI_HIDE_PASSWORD);
271 }
272
273 struct acquire_cabinet *acquire_cabinet_new_from_memory
274                 (gconstpointer file_base,size_t file_length,GnomeVFSURI *uri,gint cabinet_used)
275 {
276 struct acquire_cabinet *r;
277
278         g_return_val_if_fail(file_base!=NULL,NULL);
279         g_return_val_if_fail(uri!=NULL,NULL);
280         
281         captive_new(r);
282         r->base=(/* de-const */ gpointer)file_base;
283         r->base_cached=NULL;
284         r->offset=0;
285         r->handlep=NULL;
286         r->size=file_length;
287         acquire_cabinet_set_uri(r,uri);
288         r->cabinet_done=0;
289         r->cabinet_used=cabinet_used;
290
291         return r;
292 }
293
294 struct acquire_cabinet *acquire_cabinet_new_from_handle
295                 (GnomeVFSHandle **handlep,GnomeVFSFileInfo *file_info,GnomeVFSURI *uri,gint cabinet_used)
296 {
297 struct acquire_cabinet *r;
298
299         g_return_val_if_fail(handlep!=NULL,NULL);
300         g_return_val_if_fail(*handlep!=NULL,NULL);
301         g_return_val_if_fail(file_info!=NULL,NULL);
302         g_return_val_if_fail(uri!=NULL,NULL);
303         
304         captive_new(r);
305         if (MAP_FAILED==(r->base=mmap(
306                         NULL,   /* start */
307                         CAPTIVE_ROUND_UP64(file_info->size,getpagesize()),      /* length */
308                         PROT_READ|PROT_WRITE,
309                         MAP_ANONYMOUS|MAP_PRIVATE       /* flags */
310                                         |MAP_NORESERVE, /* We will not probably not read the whole cabinet. */
311                         -1,     /* fd; ignored due to MAP_ANONYMOUS */
312                         0))) {  /* offset; ignored due to MAP_ANONYMOUS */
313                 g_free(r);
314                 g_return_val_if_reached(NULL);
315                 }
316         captive_new0n(r->base_cached,CAPTIVE_ROUND_UP64(file_info->size,8)/8);
317         r->offset=0;
318         r->handlep=handlep;
319         r->size=file_info->size;
320         r->cabinet_done=0;
321         r->cabinet_used=cabinet_used;
322
323         /* Replace 'http://' by 'httpcaptive://' if system 'http' does not support seek(). */
324         gnome_vfs_uri_ref(uri);
325         if (GNOME_VFS_ERROR_NOT_SUPPORTED==gnome_vfs_seek(
326                         *handlep,       /* handle */
327                         GNOME_VFS_SEEK_START,   /* whence */
328                         0)) {   /* offset */
329 gchar *href;
330 const gchar *href2;
331 GnomeVFSURI *uri2;
332
333                 href=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_NONE);
334                 if (strncmp(href,"http://",strlen("http://"))) {
335                         g_warning(_("Destination file URL not valid: %s"),href);
336                         goto href_done;
337                         }
338                 href2=captive_printf_alloca("httpcaptive://%s",href+strlen("http://"));
339                 if (!(uri2=gnome_vfs_uri_new(href2))) {
340                         g_warning(_("'httpcaptive' GnomeVFS method not supported; install package 'gnomevfs-httpcaptive'; URL: %s"),href2);
341                         goto href_done;
342                         }
343                 gnome_vfs_uri_unref(uri);
344                 uri=uri2;
345 href_done:;
346                 }
347
348         acquire_cabinet_set_uri(r,uri);
349         gnome_vfs_uri_unref(uri);
350
351         return r;
352 }
353
354 void acquire_cabinet_free(struct acquire_cabinet *acquire_cabinet)
355 {
356         g_return_if_fail(acquire_cabinet!=NULL);
357
358         if (acquire_cabinet->base_cached) {
359                 munmap(acquire_cabinet->base,CAPTIVE_ROUND_UP64(acquire_cabinet->size,getpagesize()));  /* errors ignored */
360                 g_free(acquire_cabinet->base_cached);
361                 }
362         g_free((/* de-const */ gchar *)acquire_cabinet->filename);
363         gnome_vfs_uri_unref(acquire_cabinet->uri);
364         gnome_vfs_uri_unref(acquire_cabinet->handle_uri);
365         g_free(acquire_cabinet);
366 }
367
368 static struct file *file_write_fi_assertion;
369 static GByteArray *file_write_bytearray;
370
371 int file_write(struct file *fi, UBYTE *buf, size_t length)
372 {
373         g_return_val_if_fail(fi!=NULL,0);
374         g_return_val_if_fail(buf!=NULL || length==0,0);
375
376         g_return_val_if_fail(fi==file_write_fi_assertion,0);
377         g_return_val_if_fail(file_write_bytearray!=NULL,0);
378
379         if ((*ui_progress)(NULL))
380                 return 0;
381
382         g_byte_array_append(file_write_bytearray,buf,length);
383
384         return 1;       /* success */
385 }
386
387 void acquire_cabinet_load(struct acquire_cabinet *acquire_cabinet)
388 {
389 struct cabinet *basecab;
390 struct file *filelist,*fi;
391
392         g_return_if_fail(acquire_cabinet!=NULL);
393
394         if (ui_progress_bar)
395                 (*ui_progress_bar)(acquire_cabinet->cabinet_done,acquire_cabinet->cabinet_used);
396
397         if ((*ui_progress)(acquire_cabinet->uri))
398                 return;
399
400         basecab=find_cabs_in_file(acquire_cabinet);
401         if (!basecab)
402                 return;
403         if (basecab->next)
404                 return;
405         if (basecab->prevcab || basecab->nextcab)
406                 return;
407
408         filelist=process_files(basecab);
409
410         for (fi=filelist;fi;fi=fi->next) {
411 gpointer file_buffer;
412 GnomeVFSURI *uri_fi;
413 int errint;
414
415                 if (!captivemodid_module_length_is_valid(fi->length))
416                         continue;
417
418                 uri_fi=gnome_vfs_uri_append_file_name(acquire_cabinet->uri,fi->filename);
419                 if ((*ui_progress)(uri_fi)) {
420                         gnome_vfs_uri_unref(uri_fi);
421                         return;
422                         }
423
424                 file_write_fi_assertion=fi;
425                 file_write_bytearray=g_byte_array_new();
426                 /* extract_file() returns 1 for success. */
427                 errint=extract_file(fi,
428                                 0,      /* lower; ignored now */
429                                 FALSE,  /* fix */
430                                 NULL);  /* dir; ignored now */
431                 if (!errint || fi->length!=file_write_bytearray->len) {
432                         g_byte_array_free(file_write_bytearray,
433                                         TRUE);  /* free_segment */
434                         gnome_vfs_uri_unref(uri_fi);
435                         if (!errint)
436                                 return;
437                         continue;
438                         }
439                 file_buffer=g_byte_array_free(file_write_bytearray,
440                                 FALSE); /* free_segment */
441                 mod_uri_load_file_from_memory(file_buffer,fi->length,uri_fi);
442                 gnome_vfs_uri_unref(uri_fi);
443                 g_free(file_buffer);
444     }
445 }