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