+Optimization of neon "http" handler to avoid resetting connection on seek().
[captive.git] / src / install / acquire / cabinet.c
index f18695c..3967631 100644 (file)
 #include <sys/mman.h>
 #include <unistd.h>
 #include "main.h"
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/time.h>
+#include "cabinet-memory.h"
 
 #include <captive/macros.h>
 
 
+/* Config: */
+#define ACQUIRE_CABINET_READ_RAW_READ_TRY_MAX 5
+#define ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT 20
+#define ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC 0
+#define ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC 100000
+
+
+#define ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT_ITERS \
+               ((ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT *1000000LL) \
+               /(ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC*1000000LL+ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC))
+
+
 void acquire_cabinet_seek(struct acquire_cabinet *acquire_cabinet,GnomeVFSFileOffset offset)
 {
        g_return_if_fail(acquire_cabinet!=NULL);
 
-       (*ui_search_is_aborted)();
+       /* Do not: (*ui_progress)(acquire_cabinet->uri);
+        * as we currently extract some specific file out of it.
+        */
+       (*ui_progress)(NULL);
 
        acquire_cabinet->offset=offset;
 }
@@ -46,7 +65,7 @@ void acquire_cabinet_seek_skip(struct acquire_cabinet *acquire_cabinet,GnomeVFSF
 {
        g_return_if_fail(acquire_cabinet!=NULL);
 
-       (*ui_search_is_aborted)();
+       (*ui_progress)(NULL);
 
        acquire_cabinet->offset+=offset;
 }
@@ -55,11 +74,124 @@ GnomeVFSFileOffset acquire_cabinet_tell(struct acquire_cabinet *acquire_cabinet)
 {
        g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
 
-       (*ui_search_is_aborted)();
+       (*ui_progress)(NULL);
 
        return acquire_cabinet->offset;
 }
 
+static guint handler_SIGALRM_hits;
+static sigjmp_buf handler_SIGALRM_sigjmp_buf;
+
+static void handler_SIGALRM(int signo)
+{
+       g_return_if_fail(signo==SIGALRM);
+
+       /* Try to abort the read(2) call first.
+        * If it already read something it will return the partially read data.
+        * Otherwise gnome_vfs_inet_connection_read() will loop back to retry read(2)
+        * and we will abort it after 1 second. OK, some data may be read that time
+        * but who cares.
+        */
+       if (handler_SIGALRM_hits++<ACQUIRE_CABINET_READ_RAW_READ_TIMEOUT_ITERS
+                       && !(*ui_progress)(NULL))
+               return;
+
+       siglongjmp(handler_SIGALRM_sigjmp_buf,1);       /* 1; meaning: !=0 */
+}
+
+/* FIXME: This is hack.
+ * Correct way would be to use 'GnomeVFSCancellation'
+ * to abort 'GnomeVFSInetConnection' acting as 'GnomeVFSSocket'.
+ * This abort should be handled from 'http' handler
+ * but gnome_vfs_cancellation_cancel() cannot be invoked from
+ * the asynchronous slave thread.
+ */
+static GnomeVFSResult acquire_cabinet_read_raw
+               (struct acquire_cabinet *acquire_cabinet,gpointer buffer,GnomeVFSFileSize bytes,GnomeVFSFileSize *bytes_read,
+               GnomeVFSFileOffset offset)
+{
+gint try=0;
+
+       g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
+       g_return_val_if_fail(buffer!=NULL || bytes==0,GNOME_VFS_ERROR_BAD_PARAMETERS);
+       g_return_val_if_fail(bytes_read!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
+
+       *bytes_read=0;
+
+       if (!bytes)
+               return GNOME_VFS_ERROR_EOF;
+
+       while (try++<=ACQUIRE_CABINET_READ_RAW_READ_TRY_MAX) {
+GnomeVFSResult errvfsresult;
+GnomeVFSHandle *handle_new;
+struct sigaction oldact;
+int errint;
+struct itimerval itimerval;
+GnomeVFSFileSize offset_current;
+
+               if ((*ui_progress)(NULL))
+                       return GNOME_VFS_ERROR_INTERRUPTED;
+
+               if (!sigsetjmp(
+                               handler_SIGALRM_sigjmp_buf,     /* env */
+                               TRUE)) {        /* savesigs */
+                       handler_SIGALRM_hits=0;
+                       errint=sigaction(
+                                       SIGALRM,        /* signum */
+                                       NULL,   /* act */
+                                       &oldact);       /* oldact */
+                       g_assert(errint==0);
+                       signal(SIGALRM,handler_SIGALRM);
+                       itimerval.it_interval.tv_sec=ACQUIRE_CABINET_READ_RAW_READ_ITER_SEC;
+                       itimerval.it_interval.tv_usec=ACQUIRE_CABINET_READ_RAW_READ_ITER_USEC;
+                       itimerval.it_value=itimerval.it_interval;
+                       errint=setitimer(
+                                       ITIMER_REAL,    /* which */
+                                       &itimerval,     /* value */
+                                       NULL);  /* ovalue */
+                       g_assert(errint==0);
+                       /* Optimization avoid resetting connection
+                        * in neon "http" handler of: FC4 gnome-vfs2-2.10.0-5
+                        * http://bugzilla.gnome.org/show_bug.cgi?id=324984
+                        */
+                       errvfsresult=gnome_vfs_tell(*acquire_cabinet->handlep,&offset_current);
+                       if (GNOME_VFS_OK==errvfsresult && (GnomeVFSFileOffset)offset_current!=offset)
+                               errvfsresult=gnome_vfs_seek(*acquire_cabinet->handlep,GNOME_VFS_SEEK_START,offset);
+                       if (GNOME_VFS_OK==errvfsresult)
+                               errvfsresult=gnome_vfs_read(*acquire_cabinet->handlep,buffer,bytes,bytes_read);
+                       }
+               else
+                       errvfsresult=GNOME_VFS_ERROR_INTERRUPTED;
+               itimerval.it_interval.tv_sec=0;
+               itimerval.it_interval.tv_usec=0;
+               itimerval.it_value=itimerval.it_interval;
+               errint=setitimer(
+                               ITIMER_REAL,    /* which */
+                               &itimerval,     /* value */
+                               NULL);  /* ovalue */
+               g_assert(errint==0);
+               errint=sigaction(
+                               SIGALRM,        /* signum */
+                               &oldact,        /* act */
+                               NULL);  /* oldact */
+               g_assert(errint==0);
+               if (errvfsresult==GNOME_VFS_OK) {
+                       g_assert(*bytes_read>0);
+                       return GNOME_VFS_OK;
+                       }
+
+               /* Reopen '*acquire_cabinet->handlep' */
+               g_assert(acquire_cabinet->handle_uri!=NULL);
+               if (GNOME_VFS_OK==(errvfsresult=gnome_vfs_open_uri(&handle_new,acquire_cabinet->handle_uri,
+                                       GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_RANDOM))) {
+                       gnome_vfs_close(*acquire_cabinet->handlep);     /* errors ignored */
+                       *acquire_cabinet->handlep=handle_new;
+                       }
+               }
+
+       return GNOME_VFS_ERROR_IO;
+}
+
 #define ACQUIRE_CABINET_BYTE_CACHED(acquire_cabinet,pos)     (!(acquire_cabinet)->base_cached \
                                                            || (acquire_cabinet)->base_cached[(pos)/8] &  1<<((pos)&7))
 #define ACQUIRE_CABINET_SET_BYTE_CACHED(acquire_cabinet,pos) ((acquire_cabinet)->base_cached[(pos)/8] |= 1<<((pos)&7))
@@ -74,14 +206,15 @@ GnomeVFSFileSize bytes_read_now;
        g_return_val_if_fail(acquire_cabinet!=NULL,GNOME_VFS_ERROR_BAD_PARAMETERS);
        g_return_val_if_fail(buffer!=NULL || bytes==0,GNOME_VFS_ERROR_BAD_PARAMETERS);
 
-       if ((*ui_search_is_aborted)())
+       *bytes_read=0;
+
+       if ((*ui_progress)(NULL))
                return GNOME_VFS_ERROR_INTERRUPTED;
 
        bytes=MAX(0,MIN(bytes,acquire_cabinet->size-acquire_cabinet->offset));
        if (!bytes)
                return GNOME_VFS_ERROR_EOF;
 
-       *bytes_read=0;
        while (bytes) {
                read_behind =acquire_cabinet->offset+bytes;
 
@@ -91,13 +224,14 @@ GnomeVFSFileSize bytes_read_now;
                while (offset_end<read_behind && !ACQUIRE_CABINET_BYTE_CACHED(acquire_cabinet,offset_end))
                        offset_end++;
                if (offset_end>offset_start) {
-                       if (GNOME_VFS_OK!=(errvfsresult=gnome_vfs_seek(acquire_cabinet->handle,GNOME_VFS_SEEK_START,offset_start)))
-                               return errvfsresult;
-                       errvfsresult=gnome_vfs_read(acquire_cabinet->handle,
-                                       acquire_cabinet->base+offset_start,offset_end-offset_start,&bytes_read_now);
+                       errvfsresult=acquire_cabinet_read_raw(acquire_cabinet,
+                                       acquire_cabinet->base+offset_start,offset_end-offset_start,&bytes_read_now,offset_start);
                        if (errvfsresult!=GNOME_VFS_OK)
                                return errvfsresult;
                        g_assert(bytes_read_now>0);
+                       acquire_cabinet->cabinet_done+=bytes_read_now;
+                       if (ui_progress_bar)
+                               (*ui_progress_bar)(acquire_cabinet->cabinet_done,acquire_cabinet->cabinet_used);
                        while (bytes_read_now) {
                                ACQUIRE_CABINET_SET_BYTE_CACHED(acquire_cabinet,offset_start);
                                offset_start++;
@@ -141,10 +275,12 @@ GnomeVFSURI *uri_cabextract;
        uri_cabextract->parent=gnome_vfs_uri_dup(uri);
 
        acquire_cabinet->uri=uri_cabextract;
+       acquire_cabinet->handle_uri=gnome_vfs_uri_ref(uri);
        acquire_cabinet->filename=gnome_vfs_uri_to_string(acquire_cabinet->uri,GNOME_VFS_URI_HIDE_PASSWORD);
 }
 
-struct acquire_cabinet *acquire_cabinet_new_from_memory(gconstpointer file_base,size_t file_length,GnomeVFSURI *uri)
+struct acquire_cabinet *acquire_cabinet_new_from_memory
+               (gconstpointer file_base,size_t file_length,GnomeVFSURI *uri,gint cabinet_used)
 {
 struct acquire_cabinet *r;
 
@@ -155,32 +291,47 @@ struct acquire_cabinet *r;
        r->base=(/* de-const */ gpointer)file_base;
        r->base_cached=NULL;
        r->offset=0;
-       r->handle=NULL;
+       r->handlep=NULL;
        r->size=file_length;
        acquire_cabinet_set_uri(r,uri);
+       r->cabinet_done=0;
+       r->cabinet_used=cabinet_used;
+       r->memory=acquire_cabinet_memory_object_new();
 
        return r;
 }
 
-struct acquire_cabinet *acquire_cabinet_new_from_handle(GnomeVFSHandle *handle,GnomeVFSFileInfo *file_info,GnomeVFSURI *uri)
+struct acquire_cabinet *acquire_cabinet_new_from_handle
+               (GnomeVFSHandle **handlep,GnomeVFSFileInfo *file_info,GnomeVFSURI *uri,gint cabinet_used)
 {
 struct acquire_cabinet *r;
 
-       g_return_val_if_fail(handle!=NULL,NULL);
+       g_return_val_if_fail(handlep!=NULL,NULL);
+       g_return_val_if_fail(*handlep!=NULL,NULL);
        g_return_val_if_fail(file_info!=NULL,NULL);
        g_return_val_if_fail(uri!=NULL,NULL);
        
        captive_new(r);
        if (MAP_FAILED==(r->base=mmap(
-                       NULL,CAPTIVE_ROUND_UP64(file_info->size,getpagesize()),PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0))) {
+                       NULL,   /* start */
+                       CAPTIVE_ROUND_UP64(file_info->size,getpagesize()),      /* length */
+                       PROT_READ|PROT_WRITE,
+                       MAP_ANONYMOUS|MAP_PRIVATE       /* flags */
+                                       |MAP_NORESERVE, /* We will not probably not read the whole cabinet. */
+                       -1,     /* fd; ignored due to MAP_ANONYMOUS */
+                       0))) {  /* offset; ignored due to MAP_ANONYMOUS */
                g_free(r);
                g_return_val_if_reached(NULL);
                }
        captive_new0n(r->base_cached,CAPTIVE_ROUND_UP64(file_info->size,8)/8);
        r->offset=0;
-       r->handle=handle;
+       r->handlep=handlep;
        r->size=file_info->size;
+       r->cabinet_done=0;
+       r->cabinet_used=cabinet_used;
+
        acquire_cabinet_set_uri(r,uri);
+       r->memory=acquire_cabinet_memory_object_new();
 
        return r;
 }
@@ -195,6 +346,8 @@ void acquire_cabinet_free(struct acquire_cabinet *acquire_cabinet)
                }
        g_free((/* de-const */ gchar *)acquire_cabinet->filename);
        gnome_vfs_uri_unref(acquire_cabinet->uri);
+       gnome_vfs_uri_unref(acquire_cabinet->handle_uri);
+       g_object_unref(acquire_cabinet->memory);
        g_free(acquire_cabinet);
 }
 
@@ -209,7 +362,7 @@ int file_write(struct file *fi, UBYTE *buf, size_t length)
        g_return_val_if_fail(fi==file_write_fi_assertion,0);
        g_return_val_if_fail(file_write_bytearray!=NULL,0);
 
-       if ((*ui_search_is_aborted)())
+       if ((*ui_progress)(NULL))
                return 0;
 
        g_byte_array_append(file_write_bytearray,buf,length);
@@ -224,45 +377,62 @@ struct file *filelist,*fi;
 
        g_return_if_fail(acquire_cabinet!=NULL);
 
-       if ((*ui_search_is_aborted)())
+       if (ui_progress_bar)
+               (*ui_progress_bar)(acquire_cabinet->cabinet_done,acquire_cabinet->cabinet_used);
+
+       if ((*ui_progress)(acquire_cabinet->uri))
                return;
 
+       acquire_cabinet_memory_object_push(acquire_cabinet->memory);
+
        basecab=find_cabs_in_file(acquire_cabinet);
        if (!basecab)
-               return;
+               goto fail_memory_pop;
        if (basecab->next)
-               return;
+               goto fail_memory_pop;
        if (basecab->prevcab || basecab->nextcab)
-               return;
+               goto fail_memory_pop;
 
        filelist=process_files(basecab);
 
        for (fi=filelist;fi;fi=fi->next) {
 gpointer file_buffer;
 GnomeVFSURI *uri_fi;
+int errint;
 
                if (!captivemodid_module_length_is_valid(fi->length))
                        continue;
 
-               if ((*ui_search_is_aborted)())
-                       return;
+               uri_fi=gnome_vfs_uri_append_file_name(acquire_cabinet->uri,fi->filename);
+               if ((*ui_progress)(uri_fi)) {
+                       gnome_vfs_uri_unref(uri_fi);
+                       goto fail_memory_pop;
+                       }
 
                file_write_fi_assertion=fi;
                file_write_bytearray=g_byte_array_new();
-               extract_file(fi,
+               /* extract_file() returns 1 for success. */
+               errint=extract_file(fi,
                                0,      /* lower; ignored now */
                                FALSE,  /* fix */
                                NULL);  /* dir; ignored now */
-               if (fi->length!=file_write_bytearray->len) {
+               if (!errint || fi->length!=file_write_bytearray->len) {
                        g_byte_array_free(file_write_bytearray,
                                        TRUE);  /* free_segment */
+                       gnome_vfs_uri_unref(uri_fi);
+                       if (!errint)
+                               goto fail_memory_pop;
                        continue;
                        }
                file_buffer=g_byte_array_free(file_write_bytearray,
                                FALSE); /* free_segment */
-               uri_fi=gnome_vfs_uri_append_file_name(acquire_cabinet->uri,fi->filename);
                mod_uri_load_file_from_memory(file_buffer,fi->length,uri_fi);
                gnome_vfs_uri_unref(uri_fi);
                g_free(file_buffer);
     }
+
+       /* FALLTHRU */
+
+fail_memory_pop:
+       acquire_cabinet_memory_object_pop(acquire_cabinet->memory);
 }