Logo Search packages:      
Sourcecode: dctc version File versions  Download package

gdl.c

/* DCTC - a Direct Connect text clone for Linux
 * Copyright (C) 2001 Eric Prevoteau
 *
 * gdl.c: Copyright (C) Eric Prevoteau <www@a2pb.gotdns.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
/*
$Id: gdl.c,v 1.30 2003/12/28 08:12:38 uid68112 Exp $
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <ctype.h>
#include <netinet/in.h>
#include <limits.h>
#include <glib.h>

#include "gdl.h"
#include "display.h"
#include "network.h"
#include "action.h"
#include "var.h"
#include "user_manage.h"
#include "uaddr.h"
#include "macro.h"
#include "dc_com.h"
#include "status.h"
#include "sema.h"
#include "timed_out_string.h"
#include "tos_key.h"
#include "md_crc.h"
#include "misc.h"
#include "toolkit.h"

static GPtrArray *gdl_array=NULL;   /* array of GDL_ENTRY */
G_LOCK_DEFINE_STATIC(gdl_array);

static GList *broken_gdl_array=NULL;      /* list of BROKEN_GDL_ENTRY */
G_LOCK_DEFINE_STATIC(broken_gdl_array);

static GString *met_dir=NULL;                   /* directory where .met files are */
static int met_scan_interval=5;                 /* number of minutes between to scan of the met_dir */

static void check_if_dl_complete(GDL_ENTRY *gdle);
static int file_is_broken(GDL_ENTRY *gdle);
static void recur_del(GString *str);
static void broken_gdl_thread(void *dummy);
static int do_broken_gdl_detach(unsigned int gdl_id, int with_deletion);
static void scan_met_dir(void);

#define MAX_WAIT_TIME 420

/************************************************************/
/* scan gdl_array to find a GDL_ENTRY with the given gdl_id */
/***************************************************************/
/* output: -1=not found else it is the index in the array      */
/* NOTE: this function MUST be called when gdl_array is locked */
/***************************************************************/
static int gdl_index(unsigned int gdl_id)
{
      if(gdl_array!=NULL)
      {
            int i;
            for(i=0;i<gdl_array->len;i++)
            {
                  GDL_ENTRY *gdle;

                  gdle=g_ptr_array_index(gdl_array,i);
                  if((gdle!=NULL)&&(gdle->gdl_id==gdl_id))
                        return i;
            }
      }
      return -1;
}

/*************************************************************************/
/* count the number of GDL_DL_ENTRY having is_running and is_started set */
/*************************************************************************/
/* input: a GPtrArray of GDL_DL_ENTRY */
/* output: number of entry activated  */
/**************************************/
static int count_started_xfer(const GPtrArray *gdl_links)
{
      int nb=0;
      int i;

      if(gdl_links==NULL)
            return nb;

      for(i=0;i<gdl_links->len;i++)
      {
            GDL_DL_ENTRY *gdldle;

            gdldle=g_ptr_array_index(gdl_links,i);
            
            if((gdldle!=NULL) &&
                  (gdldle->is_running) &&
                  (gdldle->is_started)
              )
            {
                  nb++;
            }
      }
      return nb;
}

/****************************************************************************/
/* scan links of the given gdle to find a GDL_DL_ENTRY matching information */
/****************************************************************************/
/* output: -1=not found else it is the index in the array      */
/* NOTE: this function MUST be called when gdl_array is locked */
/***************************************************************/
static int gdldle_index(GDL_ENTRY *gdle, char *nickname, char *remote_fname)
{
      if(gdle->gdl_links!=NULL)
      {
            int i;

            for(i=0;i<gdle->gdl_links->len;i++)
            {
                  GDL_DL_ENTRY *gdldl;

                  gdldl=g_ptr_array_index(gdle->gdl_links,i);
                  if((gdldl!=NULL)&&
                        (!strcmp(gdldl->nickname->str,nickname))&&
                        (!strcmp(gdldl->remote_fname->str,remote_fname)))
                        return i;
            }
      }
      return -1;
}

//typedef struct {unsigned long b; unsigned long int e;} S_RNG;
typedef struct {off_t b; off_t e;} S_RNG;

static int com_s_rng(const void *a, const void *b)
{
      int u;

      u=(((S_RNG*)a)->b) - (((S_RNG*)b)->b);
      if(u!=0)
            return u;
      return (((S_RNG*)a)->e) - (((S_RNG*)b)->e);
}

/*******************************************************************/
/* group all already downloaded segments into a more usable format */
/*******************************************************************/
GArray *ranges_to_planar(GDL_ENTRY *gdle, int non_running_only)
{
      GArray *nw;
      int i;
      S_RNG tm;

      nw=g_array_new(FALSE,FALSE,sizeof(S_RNG));

      /* put already download ranges inside the array */
      if(gdle->dld_ranges!=NULL)
      {
            for(i=0;i<gdle->dld_ranges->len;i++)
            {
                  RANGE_ENTRY *re;

                  re=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,i));
                  tm.b=re->range[0];
                  tm.e=re->range[1];
                  nw=g_array_append_val(nw,tm);
            }
      }

      /* and add part of the download in progress */
      if(non_running_only==0)
      {
            if(gdle->gdl_links!=NULL)
            {
                  for(i=0;i<gdle->gdl_links->len;i++)
                  {
                        GDL_DL_ENTRY *dle;
                        dle=g_ptr_array_index(gdle->gdl_links,i);
                        if(dle->is_started==1)
                        {
                              tm.b=dle->range[0];
                              tm.e=dle->range[0]+dle->cur_dled;   /* NOTE: here, we can have empty segment but it is necessary */
                              nw=g_array_append_val(nw,tm);
                        }
                  }
            }
      }

      if(nw->len>1)
      {
            qsort(nw->data,nw->len,sizeof(S_RNG),com_s_rng);

            /* now, we can group consecutive segments */
            i=0;
            while(i<(nw->len-1))
            {
                  S_RNG *p1,*p2;

                  p1=&(g_array_index(nw,S_RNG,i));
                  p2=&(g_array_index(nw,S_RNG,i+1));

                  if(p1->e>=p2->b)        /* end of the first segment is (at least) the beginning of the second one => group them */
                  {                                         /* if we can really have a full control, we must use an exact test */
                        p1->e=p2->e;
                        nw=g_array_remove_index(nw,i+1);
                  }
                  else
                        i++;
            }
      }
      return nw;
}

/* convert the list of downloaded segment into a list of free segment */
static GArray *dl_planar_to_free_planar(GArray *dl_ar, GDL_ENTRY *gdle)
{
      S_RNG nw;
      S_RNG *p1,*p2;
      int i;

      if(dl_ar->len==0)
      {
            /* special case when nothing is availabled */
            nw.b=0;
            nw.e=gdle->total_size;
            dl_ar=g_array_append_val(dl_ar,nw);
            return dl_ar;
      }

      /* be sure the first interval starts with 0 */
      p1=&(g_array_index(dl_ar,S_RNG,0));
      if(p1->b!=0)
      {
            nw.b=0;
            nw.e=0;
            dl_ar=g_array_insert_val(dl_ar,0,nw);
      }

      /* be sure the last interval ends with the file size */
      p1=&(g_array_index(dl_ar,S_RNG,dl_ar->len-1));
      if(p1->e!=gdle->total_size)
      {
            nw.b=gdle->total_size;
            nw.e=gdle->total_size;
            dl_ar=g_array_append_val(dl_ar,nw);
      }

      /* now, it is easy. we remove the begin of the first interval, the end of the last */
      /* the begin of each interval is its old ends and the end of each interval is the old begin of the next one */
      for(i=0;i<dl_ar->len-1;i++)
      {
            p1=&(g_array_index(dl_ar,S_RNG,i));
            p2=&(g_array_index(dl_ar,S_RNG,i+1));

            p1->b=p1->e;
            p1->e=p2->b;
      }
      /* and we remove the last one */
      dl_ar=g_array_remove_index_fast(dl_ar,dl_ar->len-1);
      return dl_ar;
}

/*****************************************************************************/
/* adjust the list of free segments to be sure no segment goes above max_pos */
/*****************************************************************************/
static GArray *crop_free_planar_by_size(GArray *dl_ar, unsigned long int max_pos)
{
      int i;
      S_RNG *p1;

      i=0;
      while(i<dl_ar->len)
      {
            p1=&(g_array_index(dl_ar,S_RNG,i));
            if(p1->b>max_pos)
            {
                  dl_ar=g_array_remove_index_fast(dl_ar,i); /* we can use fast here because all segments above this one */
                                                                                                      /* start after this one so, they will be deleted */
            }
            else if(p1->e>max_pos)
            {
                  p1->e=max_pos;                /* truncate the given segment */
                  i++;
            }
            else
                  i++;
            
      }

      return dl_ar;
}

/*****************************************************************************/
/* check if the given position is the current position of a running transfer */
/*****************************************************************************/
/* output: 1=yes, 0=no */
/***********************/
static int is_an_end_range(GDL_ENTRY *gdle, unsigned long int upper_bound)
{
      int j;

      for(j=0;j<gdle->gdl_links->len;j++)
      {
            GDL_DL_ENTRY *gdldle;

            gdldle=g_ptr_array_index(gdle->gdl_links,j);

            if(gdldle->is_started==1)
            {
                  if((gdldle->range[0]+gdldle->cur_dled)==upper_bound)
                  {
                        return 1;
                  }
            }
      }
      return 0;
}

/**********************************************************************************************/
/* move free segments of dl_ar into run_dl_ar if they appear after a running transfer of gdle */
/**********************************************************************************************/
static GArray *split_free_planar_if_running(GArray *dl_ar, GArray **run_dl_ar, GDL_ENTRY *gdle)
{
      int i;

      *run_dl_ar=g_array_new(FALSE,FALSE,sizeof(S_RNG));

      i=0;
      while(i<dl_ar->len)
      {
            S_RNG *nw;

            nw=&(g_array_index(dl_ar,S_RNG,i));

            if(is_an_end_range(gdle,nw->b))
            {
                  /* the beginning of this free bloc is at end of a running download */
                  (*run_dl_ar)=g_array_append_val((*run_dl_ar),*nw);
                  dl_ar=g_array_remove_index(dl_ar,i);                        /* don't forget to always keep the bloc in order */
            }
            else
                  i++;
      }

      return dl_ar;
}

/***********************************************************/
/* compute the lowest not yet downloaded position of a GDL */
/***********************************************************/
static unsigned long lowest_free_position(GDL_ENTRY *gdle)
{
      GArray *dl_ar;
      unsigned long min_free_location=0;

      dl_ar=ranges_to_planar(gdle,0);
      if(dl_ar==NULL)
            return min_free_location;                 /* unable to compute ranges */
      dl_ar=dl_planar_to_free_planar(dl_ar,gdle);

      if(dl_ar->len>=1)
      {
            /* the first downloadable position in the file is the position of the first free segment */
            min_free_location=(g_array_index(dl_ar,S_RNG,0)).b;
      }
      g_array_free(dl_ar,TRUE);
      return min_free_location;     
}

/***************************************************/
/* allocate a download range to the given DL_ENTRY */
/***************************************************/
/* output: 0=no range allocated */
/********************************/
static int allocate_range(GDL_DL_ENTRY *gdldle, GDL_ENTRY *gdle)
{
      struct stat st;
      GArray *dl_ar;
      GArray *run_dl_ar;
      int i;
      unsigned long int max_size;
      int cur_idx;
      S_RNG *cur;

      /* this function performs 2 tasks */
      /* find a free download range */
      dl_ar=ranges_to_planar(gdle,0);
      if(dl_ar==NULL)
            return 0;               /* unable to compute ranges */

      dl_ar=dl_planar_to_free_planar(dl_ar,gdle);
      dl_ar=crop_free_planar_by_size(dl_ar,gdldle->remote_fsize);
      dl_ar=split_free_planar_if_running(dl_ar,&run_dl_ar,gdle);
      
      /* now, dl_ar and run_dl_ar are the list of all free blocks */
      /* run_dl_ar are blocks having size shrinking due to running downloads */
      /* dl_ar are non shrinking blocs */

      /* algo: if dl_ar is not empty, take the biggest free bloc inside it */
      /*       if dl_ar is empty, take the biggest free bloc inside run_dl_ar, */
      /*         the upper limit this bloc is the upper limite of the range */
      /*         the lower limit of the range is the middle of the bloc */
      if(dl_ar->len!=0)
      {     /* great, there is free non shrinking blocs */
            cur_idx=0;
            cur=&(g_array_index(dl_ar,S_RNG,cur_idx));
            max_size=cur->e-cur->b;

            /* find the biggest bloc */
            for(i=1;i<dl_ar->len;i++)
            {
                  cur=&(g_array_index(dl_ar,S_RNG,i));

                  if((cur->e-cur->b)>max_size)
                  {
                        max_size=cur->e-cur->b;
                        cur_idx=i;
                  }
            }

            /* and assign the range */
            cur=&(g_array_index(dl_ar,S_RNG,cur_idx));
            gdldle->range[0]=cur->b;
            gdldle->range[1]=cur->e;
      }
      else
      {
            if(run_dl_ar->len==0)
            {
                  /* no free non shrinking and non shrinking blocs available, abort here */
                  g_array_free(dl_ar,TRUE);
                  g_array_free(run_dl_ar,TRUE);
                  return 0;
            }

            cur_idx=0;
            cur=&(g_array_index(run_dl_ar,S_RNG,cur_idx));
            max_size=cur->e-cur->b;

            /* find the biggest bloc */
            for(i=1;i<run_dl_ar->len;i++)
            {
                  cur=&(g_array_index(run_dl_ar,S_RNG,i));

                  if((cur->e-cur->b)>max_size)
                  {
                        max_size=cur->e-cur->b;
                        cur_idx=i;
                  }
            }

            /* ok, we have the biggest bloc */
            cur=&(g_array_index(run_dl_ar,S_RNG,cur_idx));
            gdldle->range[0]=cur->b+(max_size/2);           /* start in the middle of the bloc */
            gdldle->range[1]=cur->e;                                    /* and end at its end */
      }

      g_array_free(dl_ar,TRUE);
      g_array_free(run_dl_ar,TRUE);

      gdldle->cur_dled=0;                             /* reset the number of downloaded bytes */

      /* initialize temp_local_fname according to the download range */
      /* the localname is initialized to "GDL/localname/range[0]-range[1]" */
      gdldle->temp_local_fname=g_string_new("GDL");
      if(stat(gdldle->temp_local_fname->str,&st)==-1)
      {
            /* on error, abort or try to create the directory */
            if((errno!=ENOENT)||(mkdir(gdldle->temp_local_fname->str,0777)==-1))
            {     
                  g_string_free(gdldle->temp_local_fname,TRUE);
                  gdldle->temp_local_fname=NULL;
                  return 0;
            }
      }
      gdldle->temp_local_fname=g_string_append_c(gdldle->temp_local_fname,'/');
      gdldle->temp_local_fname=g_string_append(gdldle->temp_local_fname,gdle->local_fname->str);
      if(stat(gdldle->temp_local_fname->str,&st)==-1)
      {
            /* on error, abort or try to create the directory */
            if((errno!=ENOENT)||(mkdir(gdldle->temp_local_fname->str,0777)==-1))
            {     
                  g_string_free(gdldle->temp_local_fname,TRUE);
                  gdldle->temp_local_fname=NULL;
                  return 0;
            }
      }

#ifndef __USE_FILE_OFFSET64
      g_string_sprintfa(gdldle->temp_local_fname,"/%08lX-%08lX",gdldle->range[0],gdldle->range[1]);
#else
      g_string_sprintfa(gdldle->temp_local_fname,"/%016llX-%016llX",gdldle->range[0],gdldle->range[1]);
#endif
      return 1;   /* range allocated */
}

/********************************************************************************************/
/* check if the number of active connection with a user does not exceed the maximum allowed */
/* Note: started GDL sources are taken into account as well as running ones                 */
/* NOTE: gdl_array is already locked when entering in this function                         */
/********************************************************************************************/
/* output: 0= limit not reached, 1= limit reached */
/**************************************************/
static int enough_dl_connection_with_user(char *nickname, int max_cnx)
{
      int ret=0;

      if(max_cnx>=1)
      {
            int j=0;
            while(j<gdl_array->len)
            {
                  const GDL_ENTRY *gdle;
                  gdle=g_ptr_array_index(gdl_array,j);

                  if(gdle!=NULL)
                  {
                        const GPtrArray *gdl_links;

                        gdl_links=gdle->gdl_links;
                        if(gdl_links!=NULL)
                        {
                              int i;
                              for(i=0;i<gdl_links->len;i++)
                              {
                                    GDL_DL_ENTRY *gdldle;
      
                                    gdldle=g_ptr_array_index(gdl_links,i);

                                    if((gdldle!=NULL)&&(gdldle->is_running)&&(!strcmp(nickname,gdldle->nickname->str)))
                                    {
                                          max_cnx--;
                                          if(max_cnx<=0)
                                          {
                                                ret=1;
                                                goto abrt;
                                          }
                                    }
                              }
                        }
                  }
                  j++;
            }
      }

      abrt:
      return ret;
}

/*****************************/
/* try to start its download */
/********************************************************************/
/* NOTE: gdl_array is already locked when entering in this function */
/********************************************************************/
static void start_xdl(GDL_ENTRY *gdle, GDL_DL_ENTRY *gdldle, unsigned long int min_position)
{
      GString *nw;

      gdldle->last_start_time=time(NULL); /* always update time to avoid repeted try in short time */

      if(gdldle->remote_fsize<=min_position)    /* the file of this source has no free range */
            return;

      if(enough_dl_connection_with_user(gdldle->nickname->str,max_dl_per_user))
            return;

#if 0
      /* code disabled because with_dctclink=1 */
      if(!user_in_list(hub_user_list,gdldle->nickname->str))            /* user on the hub */
      {
            if((with_ddl==0)||(!check_uaddr_entry_by_name(gdldle->nickname->str)))  /* or DDL enable and user address is known */
            {
                  if(with_dctclink==0)                                                                                                    /* or inter DCTC communication is enabled */
                        return;                                                                                                                       /* is required else, we leave */
            }
      }
#endif

      gdldle->is_running=1;
      gdldle->is_started=0;

      /* make a download request */
      nw=g_string_new("");
      g_string_sprintf(nw,"/XDL|%u|%s|",gdle->gdl_id,gdldle->nickname->str);
      add_new_sim_input(0,nw->str);
      g_string_free(nw,TRUE);
}

/**************************************************************************************************/
/* check if the given nickname (ptr->nickname->str) has gdldle with is_running=1 and is_started=0 */
/**************************************************************************************************/
/* output: 1=yes, 0=no */
/***********************/
static int has_running_not_started_xdl(GDL_ENTRY *gdle, GDL_DL_ENTRY *gdldle)
{
      char *nick=gdldle->nickname->str;
      int i;

      for(i=0;i<gdle->gdl_links->len;i++)
      {
            GDL_DL_ENTRY *ptr;

            ptr=g_ptr_array_index(gdle->gdl_links,i);
            if(! ((ptr->is_running==1)&&(ptr->is_started==0)) )
                  continue;
            if(!strcmp(nick,ptr->nickname->str))
                  return 1;
      }
      return 0;
}

/*********************************************************************/
/* scan the given ENTRY and try to start downloads on inactive links */
/*********************************************************************/
/* NOTE: gdl_array is already locked when entering in this function */
/********************************************************************/
static void handle_one_gdl(GDL_ENTRY *gdle)
{
      int i;
      time_t cur_time;
      unsigned long int min_position;

      if(gdle->gdl_links==NULL)
            return;                       /* nothing can be done */

      if(count_started_xfer(gdle->gdl_links)>=max_running_source_per_gdl)
            return;                       /* enough source are running */

      cur_time=time(NULL);

      min_position=lowest_free_position(gdle);

      /* try to start downloads on inactive links */
      for(i=0;i<gdle->gdl_links->len;i++)
      {
            GDL_DL_ENTRY *ptr;

            ptr=g_ptr_array_index(gdle->gdl_links,i);

            /* as soon as this link is up, it cannot be changed */
            if(ptr->is_running)
            {
                  /* these few lines fix a bug somewhere else in the code */
                  /* sometimes, tasks are set to waiting but in fact, the real task (/XDL has been destroyed) */
                  if((ptr->is_started==0)&&((ptr->last_start_time+MAX_WAIT_TIME)<cur_time))
                  {
                        /* this XDL command has been sent more than 7 minutes and still not running ? */
                        /* this case can appear when dctc link is enabled and a try occurs */
                        ptr->is_started=0;
                        ptr->is_running=0;
                  }
                  else
                        continue;
            }

            if((ptr->last_start_time+300)>cur_time)         /* wait at least 5 minutes after an error */
                  continue;

            if(!has_running_not_started_xdl(gdle,ptr))      /* to avoid a race and forever waiting transfer (which in fact no more exist) */
                                                                                                      /* we initiate only one connection at a time to the same user */
                  start_xdl(gdle,ptr,min_position);
      }
}

/*************************************************************/
/* append the given range_entry at the end of the given file */
/*********************************************************************************/
/* input: f=FILE * to save data. The position in file is *cur_pos                */
/*        *cur_pos= current save position in f                                   */
/*       RANGE_ENTRY= segment to append (only part of the segment can be useful) */
/* output: return value !=0: it is the errno of the command making an error      */
/*         return value==0, *cur_pos is the new position in file f and this one  */
/*         contains the data.                                                    */
/*********************************************************************************/
static int append_this_range_entry_to_file(FILE *f, unsigned long *cur_pos, RANGE_ENTRY *re)
{
      FILE *add;
      unsigned long to_append;
      int a;
      char buf[8192];

      add=fopen(re->temp_local_fname->str,"rb");
      if(add==NULL)
      {
            return errno;
      }

      if(*cur_pos!=re->range[0])
      {
            /* we wont use the whole part, we have to skip the beginning */
            if(fseek(add,(*cur_pos)-re->range[0],SEEK_SET))
            {
                  a=errno;
                  fclose(add);
                  return a;
            }
      }

      /* copy the remaining part of the block at the end of the file */
      a=0;
      to_append=re->range[1]-(*cur_pos);
      while(to_append!=0)
      {
            int want,have;
            
            /* read a segment slice */
            want=MIN(sizeof(buf),to_append);

            /* get 1 slice */
            get_slice(bl_semid,GATHER_SEMA);

            have=fread(buf,1,want,add);

            if(have==-1)
            {
                  a=errno;
                  break;
            }
            if(have!=want)
            {
#ifdef ENODATA
                  a=ENODATA;              /* set the error message to "no data available */
#else
                  a=EINVAL;               /* ENODATA not available on GNU/kFreeBSD */
#endif
                  break;
            }

            /* and write it at the end of the file */
            want=fwrite(buf,1,have,f);
            if(want!=have)
            {
                  a=ferror(f);
                  break;
            }

            (*cur_pos)+=have;
            to_append-=have;
      }

      fclose(add);
      return a;
}

/*******************************************************************************************************/
/* scan the array of RANGE_ENTRY and return the index of an entry where cur_pos is between range value */
/*******************************************************************************************************/
/* output: index or -1 */
/***********************/
static int get_range_entry_for_position(GArray *dld_ranges, unsigned long cur_pos)
{
      int i;
      RANGE_ENTRY *nw;

      for(i=0;i<dld_ranges->len;i++)
      {
            nw=&(g_array_index(dld_ranges,RANGE_ENTRY,i));

            if((nw->range[0]<=cur_pos)&&(cur_pos<nw->range[1]))   /* be careful, the upper bound is excluded while the lower bound is included */
                  return i;
      }
      return -1;
}

static void start_at_end_script(char *programname, char *gdl_filename)
{
      switch(fork())
      {
            case -1:    disp_msg(ERR_MSG,"start_at_end_script","fork fails",NULL);
                              break;

            case 0:     /* the son */
                              {     /* close all files descriptors */
                                    int i;
                                    int mx;

                                    mx=sysconf(_SC_OPEN_MAX);
                                          for(i=2;i<mx;i++)
                                                close(i);
                              }

                              /* and start the program */
                              execlp(programname,programname,gdl_filename,NULL);
                              _exit(0);

            default:    return;
      }
}

/*********************************************/
/* gather part of the file into one big file */
/*********************************************/
/* output:0=success, !=0=error */
/*******************************/
static int gather_gdl_part(GDL_ENTRY *gdle)
{
      int a;
      int copy_done=0;
      GString *terminal_filename=NULL;

      if(gdle->dld_ranges->len==1)
      {
            /* 1 segment only, try to avoid the copy */
            RANGE_ENTRY *ptr=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,0));
            a=rename(ptr->temp_local_fname->str,gdle->local_fname->str);

            if(a==0)
                  copy_done=1;
            else
            {
                  disp_msg(ERR_MSG,"gather_gdl_part","mono-fragment file rename fail",ptr->temp_local_fname->str,"To",gdle->local_fname->str,strerror(a),"Trying in copy mode",NULL);
            }
      }

      if(!copy_done)
      {
            FILE *f;
            unsigned long cur_pos=0;
            int idx;

            f=fopen(gdle->local_fname->str,"wb");
            if(f==NULL)
            {
                  a=errno;
                  disp_msg(ERR_MSG,"gather_gdl_part","unable to create",gdle->local_fname->str,strerror(a),NULL);
                  return 1;         /* error */
            }

            cur_pos=0;
            while(cur_pos!=gdle->total_size)
            {
                  idx=get_range_entry_for_position(gdle->dld_ranges,cur_pos);
                  if(idx==-1)
                  {
                        disp_msg(ERR_MSG,"gather_gdl_part","Internal error, a completed GDL contains missing parts",gdle->local_fname->str,
                                                            "position","|lu",(unsigned long)cur_pos,NULL);
                        fclose(f);
                        unlink(gdle->local_fname->str);
                        return 1;         /* error */
                  }
      
                  if((a=append_this_range_entry_to_file(f,&cur_pos,&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,idx))))!=0)
                  {

                        disp_msg(ERR_MSG,"gather_gdl_part","Fail to append",(g_array_index(gdle->dld_ranges,RANGE_ENTRY,idx)).temp_local_fname->str,"at the end of",
                                                            gdle->local_fname->str,"Reason:",strerror(a),NULL);
                        fclose(f);
                        unlink(gdle->local_fname->str);
                        return 1;         /* error */
                  }
            }
            fclose(f);
      }

      /* check a CRC ? */
      if(gdle->has_crc)
      {
            int er;

            er=is_a_file_with_a_valid_crc(gdle->local_fname->str,gdle->ed2k_crc);
            if(er)
            {
                  {
                        FILE *f;
                        GString *o;
                        G_LOCK_DEFINE_STATIC(done_log);

                        o=g_string_new("");
                        g_string_sprintf(o,"%s.done",local_dctc_sock_path->str);
                        G_LOCK(done_log);

                        f=fopen(o->str,"ab");
                        if(f!=NULL)
                        {
#ifndef __USE_FILE_OFFSET64
                              fprintf(f,"(multi-source) - broken file (CRC) |XDL:%llu|%s|%lu\n",
                                                      (unsigned long long)time(NULL),gdle->local_fname->str,gdle->total_size);
#else
                              fprintf(f,"(multi-source) - broken file (CRC) |XDL:%llu|%s|%llu\n",
                                                      (unsigned long long)time(NULL),gdle->local_fname->str,gdle->total_size);
#endif
                              fclose(f);
                        }
                        G_UNLOCK(done_log);
                        g_string_free(o,TRUE);
                  }

                  /* well, the file is corrupted */
                  if(file_is_broken(gdle)==0)
                  {
                        return 0;   /* the file_is_broken function has succeeded, we can delete this GDL */
                  }
            }
      }

      {
            FILE *f;
            GString *o;
            G_LOCK_DEFINE_STATIC(done_log);

            o=g_string_new("");
            g_string_sprintf(o,"%s.done",local_dctc_sock_path->str);
            G_LOCK(done_log);

            f=fopen(o->str,"ab");
            if(f!=NULL)
            {
#ifndef __USE_FILE_OFFSET64
                  fprintf(f,"(multi-source)|XDL:%llu|%s|%lu\n",
                                          (unsigned long long)time(NULL),gdle->local_fname->str,gdle->total_size);
#else
                  fprintf(f,"(multi-source)|XDL:%llu|%s|%llu\n",
                                          (unsigned long long)time(NULL),gdle->local_fname->str,gdle->total_size);
#endif
                  fclose(f);
            }
            G_UNLOCK(done_log);
            g_string_free(o,TRUE);
      }


      /* here, we have 6 cases: */
      /* when_done flag | post_fname | post_dir */
      /*  not set       |  NULL      | NULL     : 1) (v=0) the easiest case, nothing to do */
      /*  set           |  NULL      | NULL     : 2) (v=1) move the created file into the done directory */
      /*  not set       |  not NULL  | NULL     : 3) (v=2) rename the created file to its new name */
      /*  set           |  not NULL  | NULL     : 4) (v=3) move the created file into the done directory and change its new name */
      /*  not set       |  not NULL  | not NULL : 5) (v=6) change the name of the created file and put it in a different directory */
      /*  set           |  not NULL  | not NULL : 6) (v=7) change the name of the created file and put it in a different directory located in the done directory */

      {
            int post_fname_flag, post_dir_flag;
            int global_check;

            if((gdle->post_fname==NULL)||(gdle->post_fname->len==0))
            {
                  post_fname_flag=0;
                  post_dir_flag=0;
            }
            else
            {
                  post_fname_flag=1;
                  if((gdle->post_dir==NULL)||(gdle->post_dir->len==0))
                        post_dir_flag=0;
                  else
                        post_dir_flag=1;
            }

            global_check=(when_done?1:0)|(post_fname_flag<<1)|(post_dir_flag<<2);

            /* default filename at the end */
            terminal_filename=g_string_new(gdle->local_fname->str);

            switch(global_check)
            {
                  case 0:     /* case 1: nothing */
                                    break;

                  case 1:     /* case 2: when_done */
                                    {
                                          GString *dir_part;
                                          char *t;
                                          int offset;

                                          /* source: gdle->local_fname->str */

                                          dir_part=g_string_new(gdle->local_fname->str);
                                          t=strrchr(dir_part->str,'/');
                                          if(t!=NULL)
                                          {
                                                offset=t-dir_part->str+1;       /* position just after the / */
                                          }
                                          else
                                          {
                                                offset=0;
                                          }
                                          dir_part=g_string_truncate(dir_part,offset);
                                          dir_part=g_string_append(dir_part,"done");

                                          /* create the destination directory */
                                          mkdir(dir_part->str,0777);
                              
                                          dir_part=g_string_assign(dir_part,gdle->local_fname->str);
                                          dir_part=g_string_insert(dir_part,offset,"done/");
                                          /* move the file */
                                          if(rename(gdle->local_fname->str,dir_part->str))
                                          {
                                                int err=errno;
                              
                                                disp_msg(ERR_MSG,NULL,"Fail to rename (1)",gdle->local_fname->str,"into",dir_part->str,"because:",strerror(err),NULL);
                                          }
                                          else
                                                terminal_filename=g_string_assign(terminal_filename,dir_part->str);
                                          g_string_free(dir_part,TRUE);
                                    }
                                    break;

                  case 2:     /* case 3: post_fname */
                                    {
                                          GString *dir_part;
                                          char *t;
                                          int offset;

                                          /* source: gdle->local_fname->str */

                                          dir_part=g_string_new(gdle->local_fname->str);
                                          t=strrchr(dir_part->str,'/');
                                          if(t!=NULL)
                                          {
                                                offset=t-dir_part->str+1;       /* position just after the / */
                                          }
                                          else
                                          {
                                                offset=0;
                                          }
                                          dir_part=g_string_truncate(dir_part,offset);
                                          dir_part=g_string_append(dir_part,gdle->post_fname->str);

                                          /* move the file */
                                          if(rename(gdle->local_fname->str,dir_part->str))
                                          {
                                                int err=errno;
                              
                                                disp_msg(ERR_MSG,NULL,"Fail to rename (2)",gdle->local_fname->str,"into",dir_part->str,"because:",strerror(err),NULL);
                                          }
                                          else
                                                terminal_filename=g_string_assign(terminal_filename,dir_part->str);
                                          g_string_free(dir_part,TRUE);
                                    }
                                    break;

                  case 3:     /* case 4: when_done+post_fname */
                                    {
                                          GString *dir_part;
                                          char *t;
                                          int offset;

                                          /* source: gdle->local_fname->str */

                                          dir_part=g_string_new(gdle->local_fname->str);
                                          t=strrchr(dir_part->str,'/');
                                          if(t!=NULL)
                                          {
                                                offset=t-dir_part->str+1;       /* position just after the / */
                                          }
                                          else
                                          {
                                                offset=0;
                                          }
                                          dir_part=g_string_truncate(dir_part,offset);
                                          dir_part=g_string_append(dir_part,"done");

                                          /* create the destination directory */
                                          mkdir(dir_part->str,0777);
                              
                                          dir_part=g_string_append_c(dir_part,'/');
                                          dir_part=g_string_append(dir_part,gdle->post_fname->str);
                                          /* move the file */
                                          if(rename(gdle->local_fname->str,dir_part->str))
                                          {
                                                int err=errno;
                              
                                                disp_msg(ERR_MSG,NULL,"Fail to rename (3)",gdle->local_fname->str,"into",dir_part->str,"because:",strerror(err),NULL);
                                          }
                                          else
                                                terminal_filename=g_string_assign(terminal_filename,dir_part->str);
                                          g_string_free(dir_part,TRUE);
                                    }
                                    break;

                  case 6:     /* case 5: post_fname+post_dir */
                                    {
                                          GString *dir_part;
                                          char *t;
                                          int offset;
                                          int err;

                                          /* source: gdle->local_fname->str */

                                          dir_part=g_string_new(gdle->local_fname->str);
                                          t=strrchr(dir_part->str,'/');
                                          if(t!=NULL)
                                          {
                                                offset=t-dir_part->str+1;       /* position just after the / */
                                          }
                                          else
                                          {
                                                offset=0;
                                          }
                                          dir_part=g_string_truncate(dir_part,offset);
                                          dir_part=g_string_append(dir_part,gdle->post_dir->str);
                                          if((err=recursive_mkdir(dir_part))==0)
                                          {
                                                dir_part=g_string_append_c(dir_part,'/');
                                                dir_part=g_string_append(dir_part,gdle->post_fname->str);

                                                /* move the file */
                                                if(rename(gdle->local_fname->str,dir_part->str))
                                                {
                                                      err=errno;
                              
                                                      disp_msg(ERR_MSG,NULL,"Fail to rename (6)",gdle->local_fname->str,"into",dir_part->str,"because:",strerror(err),NULL);
                                                }
                                                else
                                                      terminal_filename=g_string_assign(terminal_filename,dir_part->str);
                                          }
                                          else
                                          {
                                                disp_msg(ERR_MSG,NULL,"recursive_mkdir fails for (6)",dir_part->str,"because:",strerror(err),NULL);
                                          }
                                          g_string_free(dir_part,TRUE);
                                    }
                                    break;

                  case 7:     /* case 6: when_done+post_fname+post_dir */
                                    {
                                          GString *dir_part;
                                          char *t;
                                          int offset;
                                          int err;

                                          /* source: gdle->local_fname->str */

                                          dir_part=g_string_new(gdle->local_fname->str);
                                          t=strrchr(dir_part->str,'/');
                                          if(t!=NULL)
                                          {
                                                offset=t-dir_part->str+1;       /* position just after the / */
                                          }
                                          else
                                          {
                                                offset=0;
                                          }
                                          dir_part=g_string_truncate(dir_part,offset);
                                          dir_part=g_string_append(dir_part,"done/");
                                          dir_part=g_string_append(dir_part,gdle->post_dir->str);
                                          if((err=recursive_mkdir(dir_part))==0)
                                          {
                                                dir_part=g_string_append_c(dir_part,'/');
                                                dir_part=g_string_append(dir_part,gdle->post_fname->str);

                                                /* move the file */
                                                if(rename(gdle->local_fname->str,dir_part->str))
                                                {
                                                      err=errno;
                              
                                                      disp_msg(ERR_MSG,NULL,"Fail to rename (7)",gdle->local_fname->str,"into",dir_part->str,"because:",strerror(err),NULL);
                                                }
                                                else
                                                      terminal_filename=g_string_assign(terminal_filename,dir_part->str);
                                          }
                                          else
                                          {
                                                disp_msg(ERR_MSG,NULL,"recursive_mkdir fails for (7)",dir_part->str,"because:",strerror(err),NULL);
                                          }
                                          g_string_free(dir_part,TRUE);
                                    }
                                    break;
            }
      }

      if(terminal_filename!=NULL)
      {
            if(gdle->at_end_script!=NULL)
            {
                  start_at_end_script(gdle->at_end_script->str,terminal_filename->str);
            }
            g_string_free(terminal_filename,TRUE);
      }
      return 0;
}

/******************************/
/* delete the given directory */
/******************************/
static void wipe_temp_directory(GString *str)
{
      DIR *dir;
      struct dirent *obj;
      struct stat st;
      GString *tmp;

      dir=opendir(str->str);
      if(dir==NULL)
            return;

      tmp=g_string_new(NULL);

      /* delete all files of the given directory */
      while((obj=readdir(dir))!=NULL)
      {
            tmp=g_string_assign(tmp,str->str);
            g_string_sprintfa(tmp,"/%s",obj->d_name);

            if(stat(tmp->str,&st)==0)
            {
                  if(S_ISREG(st.st_mode))
                  {
                        unlink(tmp->str);
                  }
            }
      }
      g_string_free(tmp,TRUE);
      closedir(dir);
      rmdir(str->str);
}

/*********************************/
/* gather all files parts of GDL */
/*********************************/
static void inline gather_part_and_end_gdl(GDL_ENTRY *gdle)
{
      if(!gather_gdl_part(gdle))
      {     /* on gathering success, the GDL is destroyed */
            GString *str;

            str=g_string_new("GDL");
            str=g_string_append_c(str,'/');
            str=g_string_append(str,gdle->local_fname->str);

            do_gdl_end(gdle->gdl_id,1);         /* enable override because the is_completed==1 */
            wipe_temp_directory(str);
            g_string_free(str,TRUE);
      }
}

/**********************************************************************************************/
/* to avoid a collision of search attempt when several GDLs are started nearly simultaneously */
/* a value between 0 and auto_scan_delay/2 is computed and added to the last_scan time        */
/**********************************************************************************************/
/* output: value between 0 and auto_scan_delay/2 */
/*************************************************/
static inline unsigned int alea(void)
{
      unsigned int v;

      v=rand();         /* no need to initialize the random number generator, having the same sequence of */
                                    /* pseudo-random number is not important */

      return (v%(auto_scan_delay>>1));
}

/*************************************************************************/
/* build and send queries according to autoscan_delay and search pattern */
/***************************************************************************/
/* this function is called only when it is possible to send query to a hub */
/***************************************************************************/
static void do_gdl_autoscan(GDL_ENTRY *gdle)
{
      int i;
      time_t min_time,cur_time;

      if((gdle==NULL)||(gdle->autoscan==NULL)||(gdle->autoscan->len==0))
            return;

      if((gdle->gdl_links!=NULL) && (count_started_xfer(gdle->gdl_links)>=disable_gdl_as_when_enough_running) )
            return;                       /* enough source are running */

      cur_time=time(NULL);
      min_time=cur_time-auto_scan_delay;

      for(i=0;i<gdle->autoscan->len;i++)
      {
            GDL_AS_ENTRY *gae;

            gae=&(g_array_index(gdle->autoscan,GDL_AS_ENTRY,i));
            if((gae->last_scan<min_time)&&(gae->search_pattern!=NULL))
            {
                  GString *str;
                  str=g_string_new("");

                  LOCK_READ(user_info);
#ifndef __USE_FILE_OFFSET64
                  g_string_sprintf(str,"$Search %s:%hu T?F?%lu?%s|",                                                                                  /* the size is always important, it is at least */
#else
                  g_string_sprintf(str,"$Search %s:%hu T?F?%llu?%s|",                                                                                 /* the size is always important, it is at least */
#endif
                                                                        host_ip,gdle->autoscan_sock_port,
                                                                        gdle->total_size-1,                 /* it is not possible to ask for an exact size (N) */
                                                                                                                              /* to reduce bandwidth waste, we ask a size of at least N-1 bytes */
                                                                        gae->search_pattern);         /* it is a datatype?pattern in fact */
                  UNLOCK_READ(user_info);
                  
                  if((main_sck!=-1)&&(cnx_in_progress==0))
                  {     /* if connected to the hub, send the message to this hub */
                        add_new_sim_input(0,str->str);      /* yes, it is possible to sim_input DC command directly, not only dctc keyboard command, great isn't it ? :) */
                  }

                  /* if dctc link is enable, also send the query to other hub */
                  send_dc_line_to_dctc_link_direct(NULL,str->str);

                  send_str_to_unode(str);       /* and also to UNODE */

                  g_string_free(str,TRUE);
                  gae->last_scan=cur_time+alea();
            }
      }
}

/*******************************************************************************/
/* process incoming autoscan search result and update GDL download source list */
/*******************************************************************************/
/* this function is basically the same as the $SR one */
/******************************************************/
static void receive_and_decode_autoscan_result(GDL_ENTRY *gdle)
{
      char buf[8192];
      int ret;
      struct sockaddr_in gdl_srch_port_raddr;
      int gdl_srch_port_raddr_len;

      gdl_srch_port_raddr_len=sizeof(gdl_srch_port_raddr);
      ret=recvfrom(gdle->autoscan_sockfd,buf,sizeof(buf),MSG_NOSIGNAL,(void*)&gdl_srch_port_raddr,&gdl_srch_port_raddr_len);

      if(ret!=-1)
      {
            buf[ret]='\0';
            if(!strncmp(buf,"$SR ",4))                /* only process $SR message */
            {
                  /* buf format: "$SR nick filename\5filesize slotratio\5hubname (hubIP)|" */
                  char *nick;
                  char *fname;
                  char *fsize;
                  char *ratio;
                  unsigned long file_size;

                  nick=buf+3;
                  SKIP_SPACE(nick);
                  
                  if(*nick=='\0')
                        return;

                  fname=strchr(nick,' ');
                  if(fname==NULL)
                        return;

                  *fname++='\0';

                  if(user_has_flag(nick,"IGNORE_SRCH"))           /* ignore this user search results ? */
                        return;

                  SKIP_SPACE(fname);
                  if(*fname=='\0')
                        return;

                  fsize=strchr(fname,5);        /* search the \5 */
                  if(fsize==NULL)
                        return;
                  *fsize++='\0';

                  if(*fsize=='\0')
                        return;

                  file_size=strtoul(fsize,NULL,10);

                  ratio=strchr(fsize, ' ');
                  if(ratio==NULL)
                        return;
                  ratio++;


                  /* well, now, nick, fname and file_size are ready */
                  if(file_size==gdle->total_size)
                  {
                        /* everything is ok, we can add the source */
                        /* NOTE: we cannot call do_gdl_add because gdl_array is already locked */
                        GString *new_src;

                        new_src=g_string_new("");
                        g_string_sprintf(new_src,"/GDLADD %u|%s|%s|%lu",gdle->gdl_id,nick,fname,file_size);
                        add_new_sim_input(0,new_src->str);
                        g_string_free(new_src,TRUE);
                  }

                  /* maybe we have a free slot here */
                  {
                        int ratio_free, ratio_ttl;

                        if(sscanf(ratio,"%d/%d",&ratio_free,&ratio_ttl)==2)
                        {
                              if((ratio_free>0)&&(with_sr_wake_up)&&(!tos_entry_exists(WAKEUGDL_TOSKEY,nick,strlen(nick),0)))
                              {  /* if there is free slot and option is enabled and this user waiting downloads have not been waked up since enough time, wake up them */
                                    add_tos_entry(WAKEUGDL_TOSKEY,min_gdl_wake_up_delay,nick,strlen(nick),NULL,0);
                                    send_dc_line_to_dctc_link(NULL,-1,"/WAKEUGDL",nick,NULL);
                                    gdl_wake_up_sources(nick,1);        /* don't lock again or this will hang */
                              }
                        }
                  }
            }
      }
}

/**************************************************/
/* scans gdl_array every second and perform tasks */
/**************************************************/
static void gdl_thread(void *dummy)
{
      int i;
      fd_set rd;
      int max_fd=-1;
      time_t last_switch_time=0;
      time_t last_met_scan=0;
      time_t cur_time;

      while(1)
      {
            restart_loop:

            cur_time=time(NULL);

            FD_ZERO(&rd);
            G_LOCK(gdl_array);

            /* it is time to switch speed counter */
            if((last_switch_time+10)<cur_time)
            {
                  last_switch_time=cur_time;
                  i=0;
                  while(i<gdl_array->len)
                  {
                        GDL_ENTRY *gdle;
                        gdle=g_ptr_array_index(gdl_array,i);

                        if(gdle)
                        {
                              gdle->cur_spd=gdle->instant_spd;
                              gdle->instant_spd=0;
                        }
                        i++;
                  }
            }

            i=0;
            while(i<gdl_array->len)
            {
                  GDL_ENTRY *gdle;
                  gdle=g_ptr_array_index(gdl_array,i);

                  if(gdle==NULL)
                  {
                        g_ptr_array_remove_index_fast(gdl_array,i);
                  }
                  else
                  {
                        if(gdle->is_completed==0)
                        {
                              handle_one_gdl(g_ptr_array_index(gdl_array,i));
                              i++;
                        }
                        else
                        {
                              if(gdle->is_completed==1)
                              {
                                    /* due to the fact this gdle->is_completed==1, no action */
                                    /* will be performed by anyone, including its destruction */
                                    /* so we can release the lock and perform the end of the computation */
                                    G_UNLOCK(gdl_array);
                                    gather_part_and_end_gdl(gdle);
                                    goto restart_loop;
                              }
                              i++;
                        }
                  }

                  if(!behind_fw)
                  {     /* if we are connected to the hub or dctc link enabled, we must handle GDL autoscan */
                        do_gdl_autoscan(gdle);
                        
                        if(gdle->autoscan_sockfd!=-1)
                        {
                              FD_SET(gdle->autoscan_sockfd,&rd);
                              max_fd=MAX(max_fd,gdle->autoscan_sockfd);
                        }
                  }
            }

            G_UNLOCK(gdl_array);

            /* it is time to scan the met directory */
            if((last_met_scan+met_scan_interval*60)<cur_time)
            {
                  scan_met_dir();
                  last_met_scan=cur_time;
            }

            if((max_fd==-1)||(behind_fw))       /* no search socket or behind a firewall */
                  sleep(1);
            else
            {
                  struct timeval tv;
                  int ln;

                  /* wait at most 1 second */
                  tv.tv_sec=1;
                  tv.tv_usec=0;

                  ln=select(max_fd+1,&rd,NULL,NULL,&tv);
                  if(ln>0)
                  {
                        G_LOCK(gdl_array);
                        i=0;
                        while(i<gdl_array->len)
                        {
                              GDL_ENTRY *gdle;
                              gdle=g_ptr_array_index(gdl_array,i);
            
                              if((gdle!=NULL)&&(gdle->autoscan_sockfd!=-1)&&(FD_ISSET(gdle->autoscan_sockfd,&rd)))
                              {
                                    /* this gdle have received something. */
                                    receive_and_decode_autoscan_result(gdle);
                              }
                              i++;
                        }
                        G_UNLOCK(gdl_array);
                  }
            }
      }
      pthread_exit(NULL);
}

/**********************************************************/
/* create the GDL thread. On error, program is terminated */
/**********************************************************/
void start_gdl_thread(void)
{
      pthread_attr_t thread_attr;
      pthread_attr_t thread_attr2;
      static pthread_t thread_id; /* these 2 variables must exist as long as the thread exist */
      static pthread_t thread_id2;

      if(gdl_array==NULL)
            gdl_array=g_ptr_array_new();

      /* create the thread managing GDLs */
      pthread_attr_init (&thread_attr);
      pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
      if(pthread_create(&thread_id,&thread_attr, (void*)gdl_thread,NULL)!=0)
      {
            disp_msg(ERR_MSG,"start_gdl_thread","pthread_create failed",NULL);
            pthread_attr_destroy(&thread_attr);
            exit(1);
      }
      pthread_attr_destroy(&thread_attr);

      /* create the thread managing broken GDLs */
      pthread_attr_init (&thread_attr2);
      pthread_attr_setdetachstate(&thread_attr2, PTHREAD_CREATE_DETACHED);
      if(pthread_create(&thread_id2,&thread_attr2, (void*)broken_gdl_thread,NULL)!=0)
      {
            disp_msg(ERR_MSG,"start_gdl_thread","pthread_create failed",NULL);
            pthread_attr_destroy(&thread_attr2);
            exit(1);
      }
      pthread_attr_destroy(&thread_attr2);
}

/* --------------------------------------------------------------------------------- */
/* create a directory with the given name prefixed by a broken$ in the GDL directory */
/* --------------------------------------------------------------------------------- */
static GString *create_broken_dir_and_lock_for_file(char *fname, BROKEN_GDL_ENTRY *bge)
{
      GString *str,*str1;

      str=g_string_new("GDL");
      mkdir(str->str,0777);               /* just to avoid problem, create GDL/ */
      g_string_append(str,"/broken$");
      g_string_append(str,fname);
      mkdir(str->str,0777);               /* create GDL/filename */
      str1=g_string_new(str->str);
      g_string_append(str,"/.crc");
      bge->locked_crc_file_fd=open(str->str,O_RDWR|O_CREAT|O_TRUNC,0777);
      if(lockf(bge->locked_crc_file_fd,F_TLOCK,1))    /* enable try lock to avoid handing client */
      {
            disp_msg(ERR_MSG,"create_broken_dl_dir_and_lock","lockf failed",strerror(errno),"WARNING: error ignored",NULL);
      }
      g_string_free(str,TRUE);
      return str1;
}

/******************************************/
/* update the .cmd file of the given gdle */
/**************************************************************/
/* unlike the normal update function, not everything is saved */
/* kept entries: G(global), O(rename), S(source), F(autoscan) */
/*               P(end script).                               */
/* ignored: A(active), R(range)                               */
/**************************************************************/
static void update_gdl_cmd_of_this_broken_gdle(GDL_ENTRY *gdle, const char *broken_path)
{
      FILE *f;
      GString *str;
      int i;

      str=g_string_new(broken_path);
      str=g_string_append(str,"/.cmd");

      f=fopen(str->str,"wb");
      if(f==NULL)
      {
            disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","unable to create",str->str,NULL);
            g_string_free(str,TRUE);
            return;
      }
      
      /* create the GDL name entry */
#ifndef __USE_FILE_OFFSET64
      g_string_sprintf(str,"G|%u|%s|%lu\n",gdle->gdl_id,gdle->local_fname->str,gdle->total_size);
#else
      g_string_sprintf(str,"G|%u|%s|%llu\n",gdle->gdl_id,gdle->local_fname->str,gdle->total_size);
#endif
      if(fwrite(str->str,1,str->len,f)!=str->len)
      {
            disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
      }
      else
      {
            /* add the corrupted file as range */
#ifndef __USE_FILE_OFFSET64
            g_string_sprintf(str,"R|GDL/%s/%s|0|%lu\n",gdle->local_fname->str,gdle->local_fname->str,gdle->total_size);
#else
            g_string_sprintf(str,"R|GDL/%s/%s|0|%llu\n",gdle->local_fname->str,gdle->local_fname->str,gdle->total_size);
#endif
            if(fwrite(str->str,1,str->len,f)!=str->len)
            {
                  disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                  goto abrt;
            }

            /* add the CRC if it exists */
            if(gdle->has_crc)
            {
                  g_string_assign(str,"C|");
                  append_MD4_to_str(str,gdle->ed2k_crc);
                  g_string_append(str,"|-|-\n");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* add the partial CRC if it exists */
            if(gdle->ed2k_partial_crc)
            {
                  g_string_assign(str,"X|");
                  for(i=0;i<gdle->nb_partial_crc_seg;i++)
                  {
                        append_MD4_to_str(str,gdle->ed2k_partial_crc+i*MD4_DIGEST_LENGTH);
                  }
                  g_string_append(str,"|-|-\n");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* add file renaming if exists */
            if((gdle->post_fname!=NULL)&&(strlen(gdle->post_fname->str)))
            {
                  g_string_sprintf(str,"O|%s|%s|-\n",gdle->post_fname->str,
                                                                                          (gdle->post_dir!=NULL)?gdle->post_dir->str:"");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }
      
            /* script to start at end if exists */
            if((gdle->at_end_script!=NULL)&&(strlen(gdle->at_end_script->str)))
            {
                  g_string_sprintf(str,"P|%s|-|-\n",gdle->at_end_script->str);
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }
      
            /* put the GDL source entries */
            for(i=0;i<gdle->gdl_links->len;i++)
            {
                  GDL_DL_ENTRY *ptr;
                  ptr=g_ptr_array_index(gdle->gdl_links,i);
#ifndef __USE_FILE_OFFSET64
                  g_string_sprintf(str,"S|%s|%s|%lu\n",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#else
                  g_string_sprintf(str,"S|%s|%s|%llu\n",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#endif
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* put the autoscan pattern */
            for(i=0;i<gdle->autoscan->len;i++)
            {
                  GDL_AS_ENTRY *ptr;

                  ptr=&(g_array_index(gdle->autoscan,GDL_AS_ENTRY,i));
                  g_string_sprintf(str,"F|%s|-|-\n",ptr->search_pattern);           /* always 3 | per line */
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_broken_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }
      }

      abrt:
      fclose(f);
      g_string_free(str,TRUE);
      return;
}

/**********************************************************************/
/* create a new GDL from the given GDL having a broken file as result */
/**********************************************************************/
/* output:0=success, !=0=error */
/*******************************/
static int file_is_broken(GDL_ENTRY *gdle)
{
      GString *npath;
      GString *nname;
      BROKEN_GDL_ENTRY *bge;

      bge=malloc(sizeof(BROKEN_GDL_ENTRY));
      if(bge==NULL)
            return 1;   /* unable to solve the problem, keep the broken file */
      bge->broken_gdl_id=rand();
      bge->local_fname=g_string_new(gdle->local_fname->str);
      memcpy(bge->ed2k_crc,gdle->ed2k_crc,MD4_DIGEST_LENGTH);
      bge->total_size=gdle->total_size;
      bge->last_attempt=0;
      if(gdle->ed2k_partial_crc==NULL)
            bge->ed2k_partial_crc=NULL;
      else
      {
            bge->ed2k_partial_crc=malloc(gdle->nb_partial_crc_seg*MD4_DIGEST_LENGTH);
            if(bge->ed2k_partial_crc)
                  memcpy(bge->ed2k_partial_crc,gdle->ed2k_partial_crc,gdle->nb_partial_crc_seg*MD4_DIGEST_LENGTH);
      }
            
      npath=create_broken_dir_and_lock_for_file(gdle->local_fname->str,bge);

      update_gdl_cmd_of_this_broken_gdle(gdle, npath->str);

      /* and to end this task, move the broken file into the broken directory */
      nname=g_string_new(npath->str);
      g_string_append_c(nname,'/');
      g_string_append(nname,gdle->local_fname->str);

      if(rename(gdle->local_fname->str,nname->str))
      {
            int err=errno;

            disp_msg(ERR_MSG,NULL,"Fail to rename broken file",gdle->local_fname->str,"into",nname->str,"because:",strerror(err),NULL);
            close(bge->locked_crc_file_fd);
            recur_del(npath);
            g_string_free(npath,TRUE);
            g_string_free(nname,TRUE);
            g_string_free(bge->local_fname,TRUE);
            free(bge);
            return 1;   /* unable to solve the problem, keep the broken file */
      }

      /* WARNING: past this point, we must not destroy npath->str because the only copy of the file is inside */
      G_LOCK(broken_gdl_array);
      broken_gdl_array=g_list_append(broken_gdl_array,bge);
      G_UNLOCK(broken_gdl_array);
      
      return 1;   /* unable to solve the problem, keep the broken file */
}

/* -------------------------------------------------------------------------- */
/**************************************************************/
/* the following functions are called using keyboard commands */
/**************************************************************/

static void create_dl_dir_and_lock(char *fname,GDL_ENTRY *gdle)
{
      GString *str;
      str=g_string_new("GDL");
      mkdir(str->str,0777);               /* just to avoid problem, create GDL/ */
      str=g_string_append_c(str,'/');
      str=g_string_append(str,fname);
      mkdir(str->str,0777);               /* create GDL/filename */
      str=g_string_append(str,"/.lock");
      gdle->lock_fd=open(str->str,O_RDWR|O_CREAT|O_TRUNC,0777);
      if(lockf(gdle->lock_fd,F_TLOCK,1))  /* enable try lock to avoid handing client */
      {
            disp_msg(ERR_MSG,"create_dl_dir_and_lock","lockf failed",strerror(errno),"WARNING: error ignored",NULL);
      }

      g_string_free(str,TRUE);
}

/*********************************************************************/
/* check if the given filename does not still exist in GDL directory */
/* if it exist, try to lock the file .lock. If it does not exist,    */
/* return 0 (=not exist), if it exists and lock fails, return 1      */
/* (=exist) and *is_running=1 (someone else runs it). Else *is_running*/
/* =0 (nobody else runs it)                                          */
/* NOTE: if .lock file does not exist, lock of the .crc file is tried*/
/*********************************************************************/
/* *gdl_type is set to the type of GDL. 0=undefined, 1=normal, */
/* 2=broken GDL                                                */
/***************************************************************/
static int this_gdl_still_exists(char *local_filename,int *is_running, int *gdl_type)
{
      int ret=0;
      GString *str;
      struct stat st;
      int fd;

      str=g_string_new("GDL");
      mkdir(str->str,0777);               /* just to avoid problem */
      g_string_append_c(str,'/');
      g_string_append(str,local_filename);

      *gdl_type=0;      /* undefined GDL */

      if(stat(str->str,&st)==0)
      {
            find_subdir:
            if(S_ISDIR(st.st_mode))
            {
                  int org_len=str->len;
                  g_string_append(str,"/.lock");
                  
                  fd=open(str->str,O_RDWR);
                  if(fd==-1)
                  {     /* lock file does not exist */
                        if(errno==EISDIR)
                        {
                              disp_msg(ERR_MSG,"this_gdl_still_exists",str->str,"exists but is not a file as expected",NULL);
                              *is_running=1;
                              ret=1;
                        }
                        else if(errno==ENOENT)
                        {
                              /* no .lock but perhaps a .crc */
                              g_string_truncate(str,org_len);
                              g_string_append(str,"/.crc");

                              fd=open(str->str,O_RDWR);
                              if(fd==-1)
                              {
                                    if(errno==EISDIR)
                                    {
                                          disp_msg(ERR_MSG,"this_gdl_still_exists",str->str,"exists but is not a file as expected",NULL);
                                          *is_running=1;
                                          ret=1;
                                    }
                                    else
                                    {
                                          /* so, there is nothing, assume it is not a GDL */
                                    }
                              }
                              else
                              {
                                    *gdl_type=2;            /* broken GDL */
                                    goto lock_attempt;
                              }
                        }
                        else
                        {
                              /* so, there is nothing, assume it is not a GDL */
                        }
                  }
                  else
                  {     /* lock file exists */
                        *gdl_type=1;            /* normal GDL */
                        lock_attempt:
                        ret=1;            /* the lock exists */

                        /* test the lock */
                        if(lockf(fd,F_TEST,1))
                        {     
                              *is_running=1;    
                        }
                        else
                              *is_running=0;          /* able to lock -> nobody uses it */
                        close(fd);
                  }
            }
            else
            {
                  disp_msg(ERR_MSG,"this_gdl_still_exists",str->str,"exists but is not a directory as expected",NULL);
                  *is_running=1;
                  ret=1;
            }
      }
      else
      {
            g_string_assign(str,"GDL/broken$");
            g_string_append(str,local_filename);
            if(stat(str->str,&st)==0)
            {
                  goto find_subdir;
            }
      }
      g_string_free(str,TRUE);
      return ret;
}

/******************************************/
/* update the .cmd file of the given gdle */
/******************************************/
static void update_gdl_cmd_of_this_gdle(GDL_ENTRY *gdle)
{
      FILE *f;
      GString *str;
      int i;

      str=g_string_new("GDL/");
      str=g_string_append(str,gdle->local_fname->str);
      str=g_string_append(str,"/.cmd");

      f=fopen(str->str,"wb");
      if(f==NULL)
      {
            disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","unable to create",str->str,NULL);
            g_string_free(str,TRUE);
            return;
      }
      
      /* create the GDL name entry */
#ifndef __USE_FILE_OFFSET64
      g_string_sprintf(str,"G|%u|%s|%lu\n",gdle->gdl_id,gdle->local_fname->str,gdle->total_size);
#else
      g_string_sprintf(str,"G|%u|%s|%llu\n",gdle->gdl_id,gdle->local_fname->str,gdle->total_size);
#endif
      if(fwrite(str->str,1,str->len,f)!=str->len)
      {
            disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
      }
      else
      {
            /* add the CRC if it exists */
            if(gdle->has_crc)
            {
                  g_string_assign(str,"C|");
                  append_MD4_to_str(str,gdle->ed2k_crc);
                  g_string_append(str,"|-|-\n");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* add the partial CRC if it exists */
            if(gdle->ed2k_partial_crc)
            {
                  g_string_assign(str,"X|");
                  for(i=0;i<gdle->nb_partial_crc_seg;i++)
                  {
                        append_MD4_to_str(str,gdle->ed2k_partial_crc+i*MD4_DIGEST_LENGTH);
                  }
                  g_string_append(str,"|-|-\n");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* add file renaming if exists */
            if((gdle->post_fname!=NULL)&&(strlen(gdle->post_fname->str)))
            {
                  g_string_sprintf(str,"O|%s|%s|-\n",gdle->post_fname->str,
                                                                                          (gdle->post_dir!=NULL)?gdle->post_dir->str:"");
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }
      
            /* script to start at end if exists */
            if((gdle->at_end_script!=NULL)&&(strlen(gdle->at_end_script->str)))
            {
                  g_string_sprintf(str,"P|%s|-|-\n",gdle->at_end_script->str);
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }
      
            /* put the GDL source entries */
            for(i=0;i<gdle->gdl_links->len;i++)
            {
                  GDL_DL_ENTRY *ptr;
                  ptr=g_ptr_array_index(gdle->gdl_links,i);
#ifndef __USE_FILE_OFFSET64
                  g_string_sprintf(str,"S|%s|%s|%lu\n",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#else
                  g_string_sprintf(str,"S|%s|%s|%llu\n",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#endif
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }

                  /* if this entry is active, we also add its file to the list */
                  if((ptr->is_running)&&(ptr->is_started)&&(ptr->temp_local_fname!=NULL))
                  {
#ifndef __USE_FILE_OFFSET64
                        g_string_sprintf(str,"A|%s|%lu|-\n",ptr->temp_local_fname->str,ptr->range[0]);            /* always 3 | per line */
#else
                        g_string_sprintf(str,"A|%s|%llu|-\n",ptr->temp_local_fname->str,ptr->range[0]);           /* always 3 | per line */
#endif
                        if(fwrite(str->str,1,str->len,f)!=str->len)
                        {
                              disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                              goto abrt;
                        }
                  }
            }

            /* put the ranges */
            for(i=0;i<gdle->dld_ranges->len;i++)
            {
                  RANGE_ENTRY *ptr;
                  ptr=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,i));

#ifndef __USE_FILE_OFFSET64
                  g_string_sprintf(str,"R|%s|%lu|%lu\n",ptr->temp_local_fname->str,ptr->range[0],ptr->range[1]);
#else
                  g_string_sprintf(str,"R|%s|%llu|%llu\n",ptr->temp_local_fname->str,ptr->range[0],ptr->range[1]);
#endif
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

            /* put the autoscan pattern */
            for(i=0;i<gdle->autoscan->len;i++)
            {
                  GDL_AS_ENTRY *ptr;

                  ptr=&(g_array_index(gdle->autoscan,GDL_AS_ENTRY,i));
                  g_string_sprintf(str,"F|%s|-|-\n",ptr->search_pattern);           /* always 3 | per line */
                  if(fwrite(str->str,1,str->len,f)!=str->len)
                  {
                        disp_msg(ERR_MSG,"update_gdl_cmd_of_this_gdle","write fail",str->str,NULL);
                        goto abrt;
                  }
            }

      }

      abrt:
      fclose(f);
      g_string_free(str,TRUE);
      return;
}

/*******************************************/
/* create a new GDL_ENTRY inside gdl_array */
/*******************************************/
/* output: 0= success                  */
/*         1=gdl with same name exists */
/*         2=gdl with same idx exists  */
/***************************************/
int do_gdl_new(unsigned int gdl_id, char *local_filename, unsigned long int total_size)
{
      int ret=1;  /* default: error */
      int idx;
      int is_running;
      int gdl_type;

      if(this_gdl_still_exists(local_filename,&is_running,&gdl_type))
      {
            busy_name:
            if(is_running)
                  disp_msg(ERR_MSG,"do_gdl_new","a GDL with the same name still runs on another client",NULL);
            else
                  disp_msg(ERR_MSG,"do_gdl_new","a GDL with the same name still exists but does not run on another client",
                                                      "Use /GDLATTACH to attach it to this client",NULL);
            return ret;
      }

      {
            /* also check the file against broken GDL filename */
            gchar *partial_file=g_strconcat("broken$",local_filename,NULL);
            int aaa;

            aaa=this_gdl_still_exists(partial_file,&is_running,&gdl_type);
            g_free(partial_file);

            if(aaa)
                  goto busy_name;
      }

      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx==-1)
      {
            /* ok, it does not exist */
            GDL_ENTRY *nw;

            nw=malloc(sizeof(GDL_ENTRY));
            if(nw!=NULL)
            {
                  nw->gdl_id=gdl_id;
                  nw->start_time=time(NULL);
                  nw->is_completed=0;
                  nw->local_fname=g_string_new(local_filename);
                  nw->post_fname=NULL;
                  nw->post_dir=NULL;
                  nw->at_end_script=NULL;
                  nw->has_crc=FALSE;
                  nw->ed2k_partial_crc=NULL;
                  nw->total_size=total_size;
                  nw->gdl_links=g_ptr_array_new();
                  nw->dld_ranges=g_array_new(FALSE,FALSE,sizeof(RANGE_ENTRY));
                  nw->cur_spd=0;
                  nw->instant_spd=0;
                  nw->autoscan_sockfd=-1;
                  nw->autoscan=g_array_new(FALSE,FALSE,sizeof(GDL_AS_ENTRY));
                  create_dl_dir_and_lock(nw->local_fname->str,nw);
                  g_ptr_array_add(gdl_array,nw);
                  ret=0;
                  update_gdl_cmd_of_this_gdle(nw);
                  nw->dl_offset=0;              /* no data are in dld_ranges */
            }
      }
      else
      {
            disp_msg(ERR_MSG,"do_gdl_new","a GDL with the same number still exists",NULL);
            ret=2;
      }
      
      G_UNLOCK(gdl_array);
      SET_GSTATUS_GDL(gdl_array->len);
      return ret;
}

/************************/
/* .cmd file line type: 
- The main line, must be the first
G|gdl_id|local filename|local filesize

- download source
S|nickname|remote filename|remote filesize

- fragment having download in progress
A|local fragment name|fragment start position|-

- downloaded fragment
R|local fragment name|fragment start position|fragment end position

- search pattern
F|pattern|-|-

- local file renaming
O|local filename|local dirname|-

- program to start at end of GDL
P|local program name|-|-

- ed2k global CRC
C|md4_crc|-|-

- ed2k partial CRC
X|partial_crc|-|-

*/
/*****************************************************/
/* load the .cmd file into the given empty GDL entry */
/*****************************************************/
/* output: 0=ok, !=0=error */
/***************************/
static int load_gdl_cmd(GDL_ENTRY *gdle, char *filename)
{
      char buf[10240];
      FILE *f;
      char *t;
      gchar **fields=NULL;
      GString *str;

      str=g_string_new("GDL/");
      str=g_string_append(str,filename);
      str=g_string_append(str,"/.cmd");

      f=fopen(str->str,"rb");
      g_string_free(str,TRUE);
      if(f==NULL)
      {
            disp_msg(ERR_MSG,"load_gdl_cmd","Unable to open",filename,NULL);
            return 1;         /* error */
      }

      if(fgets(buf,sizeof(buf),f)==NULL)
      {
            abrt:
            if(fields!=NULL)
            {
                  disp_msg(ERR_MSG,"load_gdl_cmd","Erroneous file",filename,fields[0],fields[1],fields[2],fields[3],NULL);
                  g_strfreev(fields);
            }
            else
                  disp_msg(ERR_MSG,"load_gdl_cmd","Erroneous file",filename,NULL);

            fclose(f);
            return 1;         /* error */
      }

      if((t=strchr(buf,'\n'))==NULL)
            goto abrt;
      *t='\0';

      /* read the G line which describes the main GDL information */
      fields=g_strsplit(buf,"|",0);
      if((fields[0]==NULL)||(fields[1]==NULL)||(fields[2]==NULL)||(fields[3]==NULL)||(fields[4]!=NULL))
            goto abrt;

      if(strcmp(fields[0],"G"))
            goto abrt;
      
      gdle->local_fname=g_string_new(fields[2]);
      gdle->post_fname=NULL;
      gdle->post_dir=NULL;
      gdle->at_end_script=NULL;
      gdle->total_size=strtoul(fields[3],NULL,10);

      g_strfreev(fields);

      while(fgets(buf,sizeof(buf),f)!=NULL)
      {
            fields=g_strsplit(buf,"|",0);
            if((fields[0]==NULL)||(fields[1]==NULL)||(fields[2]==NULL)||(fields[3]==NULL)||(fields[4]!=NULL))
            {
                  disp_msg(ERR_MSG,"load_gdl_cmd","Erroneous line (ignored)",filename,fields[0],fields[1],fields[2],fields[3],NULL);
                  g_strfreev(fields);
                  continue;
            }

            if(!strcmp(fields[0],"S"))
            {
                  GDL_DL_ENTRY *nw;

                  nw=malloc(sizeof(GDL_DL_ENTRY));
                  if(nw==NULL)
                        goto abrt;

                  nw->nickname=g_string_new(fields[1]);
                  nw->remote_fname=g_string_new(fields[2]);
                  nw->remote_fsize=strtoul(fields[3],NULL,10);
                  nw->temp_local_fname=NULL;
                  nw->last_start_time=0;
                  nw->is_running=0;
                  nw->is_started=0;

                  g_ptr_array_add(gdle->gdl_links,nw);
            }
            else if(!strcmp(fields[0],"R"))
            {
                  RANGE_ENTRY re;

                  re.temp_local_fname=g_string_new(fields[1]);
                  re.range[0]=strtoul(fields[2],NULL,10);
                  re.range[1]=strtoul(fields[3],NULL,10);
                  gdle->dld_ranges=g_array_append_val(gdle->dld_ranges,re);

                  gdle->dl_offset+=(re.range[1]-re.range[0]);
            }
            else if(!strcmp(fields[0],"A"))
            {
                  struct stat st;

                  if((stat(fields[1],&st)==0) &&
                        (S_ISREG(st.st_mode)) &&
                        (st.st_size!=0) )
                  {
                        RANGE_ENTRY re;

                        re.temp_local_fname=g_string_new(fields[1]);
                        re.range[0]=strtoul(fields[2],NULL,10);
                        re.range[1]=re.range[0]+st.st_size;
                        gdle->dld_ranges=g_array_append_val(gdle->dld_ranges,re);
                        gdle->dl_offset+=(re.range[1]-re.range[0]);
                  }
            }
            else if(!strcmp(fields[0],"O"))
            {
                  if(gdle->post_fname)
                  {
                        g_string_free(gdle->post_fname,TRUE);
                        gdle->post_fname=NULL;
                  }

                  if(gdle->post_dir)
                  {
                        g_string_free(gdle->post_dir,TRUE);
                        gdle->post_dir=NULL;
                  }

                  if(strlen(fields[1]))
                  {
                        gdle->post_fname=g_string_new(fields[1]);

                        if(strlen(fields[2]))
                        {
                              gdle->post_dir=g_string_new(fields[2]);
                        }
                  }
                  
            }
            else if(!strcmp(fields[0],"F"))
            {
                  GDL_AS_ENTRY gae;

                  gae.last_scan=0;
                  gae.gae_id=rand();
                  gae.search_pattern=g_strdup(fields[1]);
                  gdle->autoscan=g_array_append_val(gdle->autoscan,gae);

                  if(gdle->autoscan_sockfd==-1)
                  {
                        gdle->autoscan_sockfd=_x_udp(gdl_as_port_range[0],gdl_as_port_range[1]);            /* create an UDP socket and found an unused port */
                        if(gdle->autoscan_sockfd==-1)
                        {
                              disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: unable to create UDP socket for autoscan of this GDL","|lu",(unsigned long)(gdle->gdl_id),NULL);
                        }
                        else
                        {
                              /* search query requires local port number to receive search result */
                              struct sockaddr_in lcl;
                              int len_lcl=sizeof(lcl);

                              set_non_bloquant_sock(gdle->autoscan_sockfd);
                              set_tos_sock(gdle->autoscan_sockfd,udp_tos);

                              if(getsockname(gdle->autoscan_sockfd,(void*)&lcl,&len_lcl)!=0)
                              {
                                    disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: unable to create UDP socket for autoscan of this GDL",
                                                                                                      "|lu",(unsigned long)(gdle->gdl_id),"error on getsockname",strerror(errno),NULL);
                                    close(gdle->autoscan_sockfd);
                                    gdle->autoscan_sockfd=-1;
                              }
                              else
                              {
                                    gdle->autoscan_sock_port=ntohs(lcl.sin_port);
                              }
                        }
                  }
            }
            else if(!strcmp(fields[0],"P"))
            {
                  if(gdle->at_end_script!=NULL)
                        g_string_free(gdle->at_end_script,TRUE);
                  gdle->at_end_script=g_string_new(fields[1]);
            }
            else if(!strcmp(fields[0],"C"))
            {
                  if(strlen(fields[1])!=(2*MD4_DIGEST_LENGTH))
                  {
                        disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: this GDL has an erroneous global CRC. CRC ignores","|lu",(unsigned long)(gdle->gdl_id),NULL);
                  }
                  else
                  {
                        gdle->has_crc=TRUE;
                        id_ascii_to_bin(fields[1],gdle->ed2k_crc);
                  }
            }
            else if(!strcmp(fields[0],"X"))
            {
                  if(gdle->ed2k_partial_crc!=NULL)
                  {
                        free(gdle->ed2k_partial_crc);
                        gdle->ed2k_partial_crc=NULL;
                  }

                  gdle->nb_partial_crc_seg=(gdle->total_size+PARTSIZE-1)/PARTSIZE;
                  if(strlen(fields[1])!=(gdle->nb_partial_crc_seg*2*MD4_DIGEST_LENGTH))
                  {
                        disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: this GDL has an erroneous partial CRC. CRC ignores","|lu",(unsigned long)(gdle->gdl_id),NULL);
                  }
                  else
                  {
                        gdle->ed2k_partial_crc=malloc(gdle->nb_partial_crc_seg*MD4_DIGEST_LENGTH);
                        if(gdle->ed2k_partial_crc!=NULL)
                        {
                              int seg;

                              for(seg=0;seg<gdle->nb_partial_crc_seg;seg++)
                              {
                                    id_ascii_to_bin(fields[1]+seg*2*MD4_DIGEST_LENGTH,gdle->ed2k_partial_crc+seg*MD4_DIGEST_LENGTH);
                              }
                        }
                  }
            }
            else if(!strcmp(fields[0],"#"))
            {
                  /* it is a comment, ignore it */
            }
            else
                  goto abrt;
            g_strfreev(fields);
      }
      fclose(f);
      return 0;
}

/************************************************************/
/* load the .cmd file into the given empty broken GDL entry */
/************************************************************/
/* output: 0=ok, !=0=error */
/***************************/
static int load_broken_gdl_cmd(BROKEN_GDL_ENTRY *gdle, char *filename)
{
      char buf[10240];
      FILE *f;
      char *t;
      gchar **fields=NULL;
      GString *str;

      str=g_string_new("GDL/broken$");
      str=g_string_append(str,filename);
      str=g_string_append(str,"/.cmd");

      f=fopen(str->str,"rb");
      g_string_free(str,TRUE);
      if(f==NULL)
      {
            disp_msg(ERR_MSG,"load_broken_gdl_cmd","Unable to open",filename,NULL);
            return 1;         /* error */
      }

      if(fgets(buf,sizeof(buf),f)==NULL)
      {
            abrt:
            if(fields!=NULL)
            {
                  disp_msg(ERR_MSG,"load_broken_gdl_cmd","Erroneous file",filename,fields[0],fields[1],fields[2],fields[3],NULL);
                  g_strfreev(fields);
            }
            else
                  disp_msg(ERR_MSG,"load_broken_gdl_cmd","Erroneous file",filename,NULL);

            fclose(f);
            return 1;         /* error */
      }

      if((t=strchr(buf,'\n'))==NULL)
            goto abrt;
      *t='\0';

      /* read the G line which describes the main GDL information */
      fields=g_strsplit(buf,"|",0);
      if((fields[0]==NULL)||(fields[1]==NULL)||(fields[2]==NULL)||(fields[3]==NULL)||(fields[4]!=NULL))
            goto abrt;

      if(strcmp(fields[0],"G"))
            goto abrt;
      
      /* only the size field is useful for broken GDL */
      gdle->total_size=strtoul(fields[3],NULL,10);

      g_strfreev(fields);

      while(fgets(buf,sizeof(buf),f)!=NULL)
      {
            fields=g_strsplit(buf,"|",0);
            if((fields[0]==NULL)||(fields[1]==NULL)||(fields[2]==NULL)||(fields[3]==NULL)||(fields[4]!=NULL))
            {
                  disp_msg(ERR_MSG,"load_broken_gdl_cmd","Erroneous line (ignored)",filename,fields[0],fields[1],fields[2],fields[3],NULL);
                  g_strfreev(fields);
                  continue;
            }

            /* only C and X line are useful for broken GDL */
            if(strcmp(fields[0],"C") && strcmp(fields[0],"X"))
                  continue;

            if(!strcmp(fields[0],"C"))
            {
                  if(strlen(fields[1])!=(2*MD4_DIGEST_LENGTH))
                  {
                        disp_msg(ERR_MSG,"load_gdl_cmd","FATAL ERROR: this broken_GDL has an erroneous global CRC. CRC ignores","|lu",(unsigned long)(gdle->broken_gdl_id),NULL);
                        goto abrt;
                  }
                  else
                  {
                        id_ascii_to_bin(fields[1],gdle->ed2k_crc);
                  }
            }
            else if(!strcmp(fields[0],"X"))
            {
                  int nb_partial_crc_seg;

                  if(gdle->ed2k_partial_crc!=NULL)
                  {
                        disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: this broken GDL has multiple partial CRC. only the last is used","|lu",(unsigned long)(gdle->broken_gdl_id),NULL);
                        free(gdle->ed2k_partial_crc);
                        gdle->ed2k_partial_crc=NULL;
                  }

                  nb_partial_crc_seg=(gdle->total_size+PARTSIZE-1)/PARTSIZE;
                  if(strlen(fields[1])!=(nb_partial_crc_seg*2*MD4_DIGEST_LENGTH))
                  {
                        disp_msg(ERR_MSG,"load_gdl_cmd","WARNING: this broken GDL has an erroneous partial CRC. CRC ignores","|lu",(unsigned long)(gdle->broken_gdl_id),NULL);
                  }
                  else
                  {
                        gdle->ed2k_partial_crc=malloc(nb_partial_crc_seg*MD4_DIGEST_LENGTH);
                        if(gdle->ed2k_partial_crc!=NULL)
                        {
                              int seg;

                              for(seg=0;seg<nb_partial_crc_seg;seg++)
                              {
                                    id_ascii_to_bin(fields[1]+seg*2*MD4_DIGEST_LENGTH,gdle->ed2k_partial_crc+seg*MD4_DIGEST_LENGTH);
                              }
                        }
                  }
            }
            else
                  goto abrt;
            g_strfreev(fields);
      }
      fclose(f);
      return 0;
}

/**********************************************************************/
/* try to attach an unused GDL named "filename" to the current client */
/**********************************************************************/
void do_gdl_attach(char *filename)
{
      int is_running;
      int gdl_type;

      G_LOCK(gdl_array);
      if(this_gdl_still_exists(filename,&is_running,&gdl_type)==0)
      {
            disp_msg(ERR_MSG,"do_gdl_attach","no GDL has this name",filename,NULL);
      }
      else
      {
            if(is_running)
            {
                  disp_msg(ERR_MSG,"do_gdl_attach","This GDL cannot be attached, it is still in use",filename,NULL);
            }
            else
            {
                  if(gdl_type==1)         /* standard GDL */
                  {
                        int i;
                        unsigned int gdl_id;
                        GDL_ENTRY *nw;

                        /* find an used id */
                        gdl_id=time(NULL);
                        while(gdl_index(gdl_id)!=-1)
                        {
                              gdl_id=rand();
                        }

                        /* ok, it does not exist */

                        nw=malloc(sizeof(GDL_ENTRY));
                        if(nw!=NULL)
                        {
                              nw->gdl_id=gdl_id;
                              nw->start_time=time(NULL);
                              nw->is_completed=0;
                              nw->local_fname=NULL;
                              nw->total_size=0;
                              nw->has_crc=FALSE;
                              nw->ed2k_partial_crc=NULL;
                              nw->gdl_links=g_ptr_array_new();
                              nw->dld_ranges=g_array_new(FALSE,FALSE,sizeof(RANGE_ENTRY));
                              nw->autoscan_sockfd=-1;
                              nw->autoscan=g_array_new(FALSE,FALSE,sizeof(GDL_AS_ENTRY));
                              nw->dl_offset=0;
                              create_dl_dir_and_lock(filename,nw);
      
                              if(load_gdl_cmd(nw,filename))
                              {     /* on error, free allocated memory */
                                    if(nw->local_fname)
                                          g_string_free(nw->local_fname,TRUE);
                                    if(nw->lock_fd!=-1)
                                    {
                                          lockf(nw->lock_fd,F_ULOCK,1);
                                          close(nw->lock_fd);
                                    }
      
                                    /* free gdl_links */
                                    for(i=0;i<nw->gdl_links->len;i++)
                                    {
                                          GDL_DL_ENTRY *dle;
      
                                          dle=g_ptr_array_index(nw->gdl_links,i);
                                          if(dle!=NULL)
                                          {
                                                if(dle->nickname)
                                                      g_string_free(dle->nickname,TRUE);
                                                if(dle->remote_fname)
                                                      g_string_free(dle->remote_fname,TRUE);
                                                if(dle->temp_local_fname)
                                                      g_string_free(dle->temp_local_fname,TRUE);
                                          }
                                    }
                                    g_ptr_array_free(nw->gdl_links,TRUE);
      
                                    /* free range */
                                    for(i=0;i<nw->dld_ranges->len;i++)
                                    {
                                          RANGE_ENTRY *re;
      
                                          re=&(g_array_index(nw->dld_ranges,RANGE_ENTRY,i));
                                          if((re!=NULL)&&(re->temp_local_fname!=NULL))
                                                g_string_free(re->temp_local_fname,TRUE);
                                    }
                                    g_array_free(nw->dld_ranges,TRUE);
      
                                    /* free autoscan entries */
                                    for(i=0;i<nw->autoscan->len;i++)
                                    {
                                          GDL_AS_ENTRY *re;
      
                                          re=&(g_array_index(nw->autoscan,GDL_AS_ENTRY,i));
                                          if((re!=NULL)&&(re->search_pattern!=NULL))
                                                g_free(re->search_pattern);
                                    }
                                    g_array_free(nw->autoscan,TRUE);
                                    free(nw);
                              }
                              else
                              {     /* on success, end function */
                                    g_ptr_array_add(gdl_array,nw);
                                    update_gdl_cmd_of_this_gdle(nw);          /* reupdate the cmd */
                                    check_if_dl_complete(nw);
                              }
                        }
                  }     
                  else if(gdl_type==2)          /* broken GDL */
                  {
                        unsigned int gdl_id;
                        BROKEN_GDL_ENTRY *nw;

                        /* find an used id */
                        gdl_id=time(NULL);
                        while(gdl_index(gdl_id)!=-1)
                        {
                              gdl_id=rand();
                        }

                        /* ok, it does not exist */

                        nw=malloc(sizeof(BROKEN_GDL_ENTRY));
                        if(nw!=NULL)
                        {
                              nw->broken_gdl_id=gdl_id;
                              nw->local_fname=g_string_new(filename);
                              nw->total_size=0;
                              nw->locked_crc_file_fd=-1;
                              nw->last_attempt=0;
                              nw->ed2k_partial_crc=NULL;
                              create_broken_dir_and_lock_for_file(filename,nw);
      
                              if(load_broken_gdl_cmd(nw,filename))
                              {     /* on error, free allocated memory */
                                    if(nw->local_fname)
                                          g_string_free(nw->local_fname,TRUE);
                                    if(nw->locked_crc_file_fd!=-1)
                                    {
                                          lockf(nw->locked_crc_file_fd,F_ULOCK,1);
                                          close(nw->locked_crc_file_fd);
                                    }
                                    if(nw->ed2k_partial_crc!=NULL)
                                          free(nw->ed2k_partial_crc);
                                    free(nw);
                              }
                              else
                              {     /* on success, end function */
                                    G_LOCK(broken_gdl_array);
                                    broken_gdl_array=g_list_append(broken_gdl_array,nw);
                                    G_UNLOCK(broken_gdl_array);
                              }
                        }
                  }
                  else
                  {
                        /* unknown GDL type */
                  }
            }     
      }

      G_UNLOCK(gdl_array);
      SET_GSTATUS_GDL(gdl_array->len);
}

/***************************************************************************************************/
/* free data used by the content of the given RANGE_ENTRY. The RANGE_ENTRY itself is not destroyed */
/***************************************************************************************************/
static void free_range_entry(RANGE_ENTRY *re, int without_unlink)
{
      if(re->temp_local_fname!=NULL)
      {
            if(!without_unlink)
                  unlink(re->temp_local_fname->str);
            g_string_free(re->temp_local_fname,TRUE);
            re->temp_local_fname=NULL;
      }
}

/************************/
/* end a running thread */
/************************/
static void terminate_xfer_gdldle(GDL_DL_ENTRY *gdldle)
{
      if(gdldle->is_started==1)
      {
            GString *nw;

            nw=g_string_new("");
            g_string_sprintf(nw,"/KILL %lu",gdldle->thread_id);
            add_new_sim_input(0,nw->str);
            g_string_free(nw,TRUE);
            gdldle->is_started=0;
      }
}

/****************************************************************************/
/* remove the nth DL_ENTRY on the given GDL_ENTRY and free allocated memory */
/* if a file download is in progress, the file is deleted.                  */
/****************************************************************************/
static void unlink_and_free_dl_entry(GDL_ENTRY *gdle, int gdl_links_index,int without_unlink)
{
      GDL_DL_ENTRY *gdldle;

      gdldle=g_ptr_array_remove_index_fast(gdle->gdl_links,gdl_links_index);
      if(gdldle!=NULL)
      {
            if(gdldle->nickname)
                  g_string_free(gdldle->nickname,TRUE);
            if(gdldle->remote_fname)
                  g_string_free(gdldle->remote_fname,TRUE);
            if(gdldle->temp_local_fname)
            {
                  if(!without_unlink)
                        unlink(gdldle->temp_local_fname->str);
                  g_string_free(gdldle->temp_local_fname,TRUE);
            }
            free(gdldle);
      }
}

/******************************************/
/* Recursively delete the given directory */
/******************************************/
static void recur_del(GString *str)
{
      GString *name;
      DIR *dir;
      struct dirent *obj;

      if((str==NULL)||(str->len==0))
            return;

      dir=opendir(str->str);
      if(dir==NULL)
      {
            disp_msg(ERR_MSG,"recur_del","unable to open directory",str->str,NULL);
            return;
      }

      name=g_string_new(NULL);
      while((obj=readdir(dir))!=NULL)
      {
            if(!strcmp(obj->d_name,"."))
                  continue;
            if(!strcmp(obj->d_name,".."))
                  continue;

            g_string_sprintf(name,"%s/%s",str->str,obj->d_name);
            if(unlink(name->str)==-1)
            {
                  if(errno==EISDIR)
                        recur_del(name);
            }
      }

      g_string_free(name,TRUE);
      closedir(dir);
      rmdir(str->str);
}

/**************************************************/
/* delete the GDL directory having the given name */
/**************************************************/
static void discard_gdl_dir(GString *local_fname)
{
      GString *gdl_dir;

      gdl_dir=g_string_new("");
      g_string_sprintf(gdl_dir,"GDL/%s",local_fname->str);
      recur_del(gdl_dir);
      g_string_free(gdl_dir,TRUE);
}

/**********************************************************/
/* detach a running GDL of this client                    */
/* there is only a minor difference between do_gdl_detach */
/* and do_gdl_end, only files are not destroyed           */
/**********************************************************/
int do_gdl_detach(unsigned int gdl_id)
{
      GDL_ENTRY *gdle;
      int idx;

      G_LOCK(gdl_array);
      idx=gdl_index(gdl_id);
      if(idx==-1)
      {
            G_UNLOCK(gdl_array);

            if(do_broken_gdl_detach(gdl_id,FALSE)==0)
            {
                  return 0;
            }
            return -1;
      }

      gdle=g_ptr_array_index(gdl_array,idx);
      if(gdle->is_completed==1)
      {
            G_UNLOCK(gdl_array);
            disp_msg(ERR_MSG,"do_gdl_detach","This GDL is over, no further modification is possible",NULL);
            return -1;
      }

      gdle=g_ptr_array_remove_index_fast(gdl_array,idx);
      G_UNLOCK(gdl_array);
      SET_GSTATUS_GDL(gdl_array->len);

      /* destroy all gdldle but without unlink files */
      if(gdle->gdl_links!=NULL)
      {
            while(gdle->gdl_links->len!=0)
            {
                  GDL_DL_ENTRY *nw;
                  nw=g_ptr_array_index(gdle->gdl_links,0);

                  terminate_xfer_gdldle(nw);                                                    /* abort transfert */
            
                  unlink_and_free_dl_entry(gdle,0,1);                               /* and destroy this entry but don't free data */
            }
            g_ptr_array_free(gdle->gdl_links,TRUE);
      }

      /* free all ranges */
      if(gdle->dld_ranges!=NULL)
      {
            while(gdle->dld_ranges->len!=0)
            {
                  RANGE_ENTRY *nw;
                  nw=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,0));
                  free_range_entry(nw,1);                                                       /* without unlink files */
                  gdle->dld_ranges=g_array_remove_index_fast(gdle->dld_ranges,0);
            }
            g_array_free(gdle->dld_ranges,TRUE);
      }

      /* close autoscan socket fd */
      if(gdle->autoscan_sockfd!=-1)
      {
            shutdown(gdle->autoscan_sockfd,2);
            close(gdle->autoscan_sockfd);
      }

      /* free all autoscan entries */
      if(gdle->autoscan!=NULL)
      {
            int i;

            for(i=0;i<gdle->autoscan->len;i++)
            {
                  gchar *sp;

                  sp=g_array_index(gdle->autoscan,GDL_AS_ENTRY,i).search_pattern;

                  if(sp!=NULL)
                        free(sp);
            }

            g_array_free(gdle->autoscan,TRUE);
      }

      if(gdle->lock_fd!=-1)
      {     /* release .lock file */
            lockf(gdle->lock_fd,F_ULOCK,1);
            close(gdle->lock_fd);
      }
            
      if(gdle->local_fname)
      {
            g_string_free(gdle->local_fname,TRUE);
      }

      free(gdle);
      return 0;
}

/*********************************************/
/* add a new GDL_DL_ENTRY inside a GDL_ENTRY */
/*********************************************/
/* output: 0= success, !=0: error */
/**********************************/
int do_gdl_add(unsigned int gdl_id, char *nickname, char *remote_fname,unsigned long int remote_fsize)
{
      int ret=1;  /* default: error */
      int idx;

      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;

            gdle=g_ptr_array_index(gdl_array,idx);
            if(gdle->is_completed==0)
            {
                  idx=gdldle_index(gdle,nickname,remote_fname);
                  if(idx==-1)
                  {
                        GDL_DL_ENTRY *nw;
      
                        nw=malloc(sizeof(GDL_DL_ENTRY));
                        if(nw!=NULL)
                        {
                              nw->nickname=g_string_new(nickname);
                              nw->remote_fname=g_string_new(remote_fname);
                              nw->remote_fsize=remote_fsize;
                              nw->temp_local_fname=NULL;
                              nw->last_start_time=0;
                              nw->is_running=0;
                              nw->is_started=0;

                              g_ptr_array_add(gdle->gdl_links,nw);
                              ret=0;
                              update_gdl_cmd_of_this_gdle(gdle);
                        }
                  }
                  else
                  {     /* a gdldle still exists, just update its size */
                        GDL_DL_ENTRY *nw;
                        nw=g_ptr_array_index(gdle->gdl_links,idx);
                        nw->remote_fsize=remote_fsize;
                        ret=0;
                        update_gdl_cmd_of_this_gdle(gdle);
                  }
            }
            else
            {
                  disp_msg(ERR_MSG,"do_gdl_add","This GDL is over, no further modification is possible",NULL);
            }
      }
      else
      {
            disp_msg(ERR_MSG,"do_gdl_add","No existing GDLs have this ID",NULL);
      }
      
      G_UNLOCK(gdl_array);

      return ret;
}

/********************************************/
/* delete a GDL_DL_ENTRY inside a GDL_ENTRY */
/********************************************/
/* output: 0= success, !=0: error */
/**********************************/
int do_gdl_del(unsigned int gdl_id, char *nickname, char *remote_fname)
{
      int ret=1;  /* default: error */
      int idx;

      G_LOCK(gdl_array);

      /* first, check if the given index exists */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;
            gdle=g_ptr_array_index(gdl_array,idx);

            if(gdle->is_completed==0)
            {
                  idx=gdldle_index(gdle,nickname,remote_fname);
                  if(idx!=-1)
                  {     /* a gdldle still exists, just update its size */
                        GDL_DL_ENTRY *nw;
                        nw=g_ptr_array_index(gdle->gdl_links,idx);
      
                        terminate_xfer_gdldle(nw);                                                    /* abort transfert */
                        
                        unlink_and_free_dl_entry(gdle,idx,0);                                   /* and destroy this entry */
                        ret=0;
                        update_gdl_cmd_of_this_gdle(gdle);
                  }
            }
            else
            {
                  disp_msg(ERR_MSG,"do_gdl_del","This GDL is over, no further modification is possible",NULL);
            }
      }
      else
      {
            if(do_broken_gdl_detach(gdl_id,TRUE)!=0)
            {
                  disp_msg(ERR_MSG,"do_gdl_del","No [broken] existing GDL has this ID",NULL);
            }
      }
      
      G_UNLOCK(gdl_array);

      return ret;
}

/***********************************/
/* delete a GDL_ENTRY of gdl_array */
/********************************************************************************/
/* input: override must always be ==0. The only case where it is !=0 is when    */
/*        the GDL thread call this function after successfully gather all parts */
/*        of the downloaded files.                                              */
/********************************************************************************/
/* output: 0= success, !=0: error */
/**********************************/
int do_gdl_end(unsigned int gdl_id,int override)
{
      GDL_ENTRY *gdle;
      int idx;

      G_LOCK(gdl_array);
      idx=gdl_index(gdl_id);
      if(idx==-1)
      {
            G_UNLOCK(gdl_array);
            return -1;
      }

      /* remove the entry of the gdl_array and release the lock */
      /* NOTE: it is only possible to remove an entry if it is not completed */
      /*       or completed but using the override flag (GDL thread only)    */
      if(override==0)
      {
            gdle=g_ptr_array_index(gdl_array,idx);
            if(gdle->is_completed==1)
            {
                  G_UNLOCK(gdl_array);
                  disp_msg(ERR_MSG,"do_gdl_end","This GDL is over, no further modification is possible",NULL);
                  return -1;
            }
      }
      gdle=g_ptr_array_remove_index_fast(gdl_array,idx);
      G_UNLOCK(gdl_array);
      SET_GSTATUS_GDL(gdl_array->len);

      /* destroy all gdldle */
      if(gdle->gdl_links!=NULL)
      {
            while(gdle->gdl_links->len!=0)
            {
                  GDL_DL_ENTRY *nw;
                  nw=g_ptr_array_index(gdle->gdl_links,0);

                  terminate_xfer_gdldle(nw);                                                    /* abort transfert */
            
                  unlink_and_free_dl_entry(gdle,0,0);                               /* and destroy this entry */
            }
            g_ptr_array_free(gdle->gdl_links,TRUE);
      }

      /* free all ranges */
      if(gdle->dld_ranges!=NULL)
      {
            while(gdle->dld_ranges->len!=0)
            {
                  RANGE_ENTRY *nw;
                  nw=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,0));
                  free_range_entry(nw,0);
                  gdle->dld_ranges=g_array_remove_index_fast(gdle->dld_ranges,0);
            }
            g_array_free(gdle->dld_ranges,TRUE);
      }

      /* close autoscan socket fd */
      if(gdle->autoscan_sockfd!=-1)
      {
            shutdown(gdle->autoscan_sockfd,2);
            close(gdle->autoscan_sockfd);
      }

      /* free all autoscan entries */
      if(gdle->autoscan!=NULL)
      {
            int i;

            for(i=0;i<gdle->autoscan->len;i++)
            {
                  gchar *sp;

                  sp=g_array_index(gdle->autoscan,GDL_AS_ENTRY,i).search_pattern;

                  if(sp!=NULL)
                        free(sp);
            }

            g_array_free(gdle->autoscan,TRUE);
      }

      if(gdle->lock_fd!=-1)
      {     /* release .lock file */
            lockf(gdle->lock_fd,F_ULOCK,1);
            close(gdle->lock_fd);
      }
            
      if(gdle->local_fname)
      {
            discard_gdl_dir(gdle->local_fname);
            g_string_free(gdle->local_fname,TRUE);
      }

      if(gdle->post_fname)
            g_string_free(gdle->post_fname,TRUE);
      if(gdle->post_dir)
            g_string_free(gdle->post_dir,TRUE);
      if(gdle->at_end_script)
            g_string_free(gdle->at_end_script,TRUE);
      if(gdle->ed2k_partial_crc)
            free(gdle->ed2k_partial_crc);

      free(gdle);

      return 0;
}

/***********************************************/
/* add a new autoscan pattern to the given GDL */
/***********************************************/
void do_gdl_as_add(unsigned int gdl_id, int filetype, char *pattern)
{
      int idx;

      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;
            GDL_AS_ENTRY gae;
            GString *str;

            gdle=g_ptr_array_index(gdl_array,idx);
            
            str=g_string_new("");
            g_string_sprintf(str,"%d?%s",filetype,pattern);

            gae.last_scan=0;
            gae.gae_id=rand();            /* not safe but should be enough to avoid conflict */
            gae.search_pattern=g_strdup(str->str);

            g_string_free(str,TRUE);
            gdle->autoscan=g_array_append_val(gdle->autoscan,gae);

            update_gdl_cmd_of_this_gdle(gdle);        /* update the cmd */

            if(gdle->autoscan_sockfd==-1)
            {
                  gdle->autoscan_sockfd=_x_udp(gdl_as_port_range[0],gdl_as_port_range[1]);            /* create an UDP socket and found an unused port */
                  if(gdle->autoscan_sockfd==-1)
                  {
                        disp_msg(ERR_MSG,"do_gdl_as_add","WARNING: unable to create UDP socket for autoscan of this GDL","|lu",(unsigned long)(gdle->gdl_id),NULL);
                  }
                  else
                  {
                        /* search query requires local port number to receive search result */
                        struct sockaddr_in lcl;
                        int len_lcl=sizeof(lcl);

                        set_non_bloquant_sock(gdle->autoscan_sockfd);
                        set_tos_sock(gdle->autoscan_sockfd,udp_tos);

                        if(getsockname(gdle->autoscan_sockfd,(void*)&lcl,&len_lcl)!=0)
                        {
                              disp_msg(ERR_MSG,"do_gdl_as_add","WARNING: unable to create UDP socket for autoscan of this GDL","|lu",(unsigned long)(gdle->gdl_id),"error on getsockname",strerror(errno),NULL);
                              close(gdle->autoscan_sockfd);
                              gdle->autoscan_sockfd=-1;
                        }
                        else
                        {
                              gdle->autoscan_sock_port=ntohs(lcl.sin_port);
                        }
                  }
            }
      }
      else
      {
            disp_msg(ERR_MSG,"do_gdl_as_add","No existing GDLs have this ID",NULL);
      }
      
      G_UNLOCK(gdl_array);
      return;
}

/***********************************************/
/* remove an autoscan pattern to the given GDL */
/***********************************************/
void do_gdl_as_del(unsigned int gdl_id, unsigned long gae_id)
{
      int idx;

      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;
            GDL_AS_ENTRY *gae;
            int i;
            int fnd=0;

            gdle=g_ptr_array_index(gdl_array,idx);
            
            for(i=0;i<gdle->autoscan->len;i++)
            {
                  gae=&(g_array_index(gdle->autoscan,GDL_AS_ENTRY,i));
                  if(gae->gae_id==gae_id)
                  {
                        fnd=1;

                        if(gae->search_pattern!=NULL)
                              g_free(gae->search_pattern);
                        gdle->autoscan=g_array_remove_index_fast(gdle->autoscan,i);
                        break;
                  }
            }

            update_gdl_cmd_of_this_gdle(gdle);        /* update the cmd */

            if(!fnd)
            {
                  disp_msg(ERR_MSG,"do_gdl_as_del","No autoscan of this GDL has this ID",NULL);
            }
      }
      else
      {
            disp_msg(ERR_MSG,"do_gdl_as_del","No existing GDLs have this ID",NULL);
      }
      
      G_UNLOCK(gdl_array);
      return;
}

/****************************************************/
/* change the final filename [and dirname] of a GDL */
/*********************************************************************/
/* if dirname == "", only filename is altered                        */
/* if both filename and dirname == "", the file renaming is disabled */
/*********************************************************************/
void do_gdl_rename(unsigned int gdl_id, char *filename, char *dirname)
{
      int idx;
      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;

            gdle=g_ptr_array_index(gdl_array,idx);

            if((filename==NULL)||(strlen(filename)==0))
            {
                  /* no filename -> no rename */
                  if(gdle->post_fname!=NULL)
                  {
                        g_string_free(gdle->post_fname,TRUE);
                        gdle->post_fname=NULL;
                  }
                  if(gdle->post_dir!=NULL)
                  {
                        g_string_free(gdle->post_dir,TRUE);
                        gdle->post_dir=NULL;
                  }
            }
            else
            {
                  /* a filename is given */
                  if(gdle->post_fname!=NULL)
                        gdle->post_fname=g_string_assign(gdle->post_fname,filename);
                  else
                        gdle->post_fname=g_string_new(filename);

                  if(gdle->post_dir==NULL)
                        gdle->post_dir=g_string_new("");
                  
                  if(dirname!=NULL)
                  {
                        gdle->post_dir=g_string_assign(gdle->post_dir,dirname);
                  }
            }
            update_gdl_cmd_of_this_gdle(gdle);        /* update the cmd */
      }
      
      G_UNLOCK(gdl_array);
      return;
}

/***************************************************************/
/* change the name of the program to start at the end of a GDL */
/***************************************************************/
/* if filename is NULL or == "", the file renaming is disabled */
/***************************************************************/
void do_gdl_script(unsigned int gdl_id, char *programname)
{
      int idx;
      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;

            gdle=g_ptr_array_index(gdl_array,idx);

            if((programname==NULL)||(strlen(programname)==0))
            {
                  /* no programname -> no script */
                  if(gdle->at_end_script!=NULL)
                  {
                        g_string_free(gdle->at_end_script,TRUE);
                        gdle->at_end_script=NULL;
                  }
            }
            else
            {
                  /* a programname is given */
                  if(gdle->at_end_script!=NULL)
                        gdle->at_end_script=g_string_assign(gdle->at_end_script,programname);
                  else
                        gdle->at_end_script=g_string_new(programname);
            }
            update_gdl_cmd_of_this_gdle(gdle);        /* update the cmd */
      }
      
      G_UNLOCK(gdl_array);
      return;
}

/***************************/
/* set or remove a GDL crc */
/**************************************************************************/
/* if md_crc==NULL, the CRC is removed                                    */
/* if md_crc!=NULL, it is a CRC stored in a MD4_DIGEST_LENGTH byte buffer */
/**************************************************************************/
void do_gdl_set_crc(unsigned int gdl_id, guint8 *md_crc)
{
      int idx;
      G_LOCK(gdl_array);

      /* first, check if the given index is not already used */
      idx=gdl_index(gdl_id);
      if(idx!=-1)
      {     
            GDL_ENTRY *gdle;

            gdle=g_ptr_array_index(gdl_array,idx);

            if(md_crc==NULL)
                  gdle->has_crc=FALSE;
            else
            {
                  memcpy(gdle->ed2k_crc,md_crc,MD4_DIGEST_LENGTH);
                  gdle->has_crc=TRUE;
                  
            }
            update_gdl_cmd_of_this_gdle(gdle);        /* update the cmd */
      }
      
      G_UNLOCK(gdl_array);
      return;
}

/************************************************************/
/* call this function to update already existing socket TOS */
/************************************************************/
void gdl_alter_socket_tos(void)
{
      int i;

      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            for(i=0;i<gdl_array->len;i++)
            {
                  GDL_ENTRY *gdle;

                  gdle=g_ptr_array_index(gdl_array,i);

                  if(gdle->autoscan_sockfd!=-1)
                        set_tos_sock(gdle->autoscan_sockfd,udp_tos);
            }
      }
      G_UNLOCK(gdl_array);
      return;
}


/* -------------------------------------------------------------------------- */
/*************************************************************************/
/* the following functions are used by the standard download function to */
/* return status of transfert.                                           */
/*************************************************************************/

/**************************************/
/* check if the given GDL is complete */
/**************************************/
static void check_if_dl_complete(GDL_ENTRY *gdle)
{
      GArray *dl_ar;
      S_RNG *one;

      if(gdle==NULL)
            return;

      dl_ar=ranges_to_planar(gdle,1);
      if(dl_ar==NULL)
            return;                 /* we may have an error later here because complete GDL are not gathered */

      if(dl_ar->len!=1)
      {
            g_array_free(dl_ar,TRUE);
            return;
      }

      /* we must have one bloc begining at 0 and ending at the end of the wanted size */
      one=&(g_array_index(dl_ar,S_RNG,0));
      if((one->b!=0)||(one->e!=gdle->total_size))
      {
            g_array_free(dl_ar,TRUE);
            return;
      }
      
      /* Yes ... we have everything, it is time to mix this produce one big file */
      gdle->is_completed=1;         /* NOTE: we can't gather file parts here because it can take a long time */
                                                            /* and gdl_array is locked so all GDL xfers are waiting */

#if 0
      printf("gdl_post_download_update done\n");
#endif
}


/**************************************************************************/
/* this function is called when a queued GDL download has a download slot */
/**************************************************************************/
/* output: 0= a range has been assigned */
/*        !=0 no range available        */
/******************************************************************/
/* if a range is assigned, *str is contains the $Get line to send */
/* and *lfile contains the name of the local filename and         */
/* *start_pos is the download start position (for information)    */
/******************************************************************/
int do_gdl_start(unsigned int gdl_id, char *nickname, pthread_t thread_id, GString **str,GString **lfile, unsigned long *start_pos)
{
      int ret=-1;
      int i;
      int j;
      GDL_ENTRY *gdle=NULL;
      GDL_DL_ENTRY *gdldle;

      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
            for(i=0;i<gdl_array->len;i++)
            {
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle->gdl_id==gdl_id)
                  {
                        if(gdle->gdl_links!=NULL)
                        {
                              for(j=0;j<gdle->gdl_links->len;j++)
                              {
                                    gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                    disp_msg(DEBUG_MSG,"do_gdl_start","|lu",gdl_id,"|d",j,"|d",(int)(gdldle->is_started),NULL);
                                    if((gdldle->is_started==0)&&(gdldle->is_running==1))  /* we can only start a not yet started function */
                                                                                                                                                /* we first try to find a xfer in the Trying status */
                                    {
                                          if(!strcmp(gdldle->nickname->str,nickname))
                                          {
                                                if(allocate_range(gdldle,gdle))
                                                {
                                                      gdldle->is_started=1;
                                                      gdldle->thread_id=thread_id;
                                                      ret=0;

                                                      *str=g_string_new("");
#ifndef __USE_FILE_OFFSET64
                                                      g_string_sprintf(*str,"$Get %s$%lu",gdldle->remote_fname->str,gdldle->range[0]+1);
#else
                                                      g_string_sprintf(*str,"$Get %s$%llu",gdldle->remote_fname->str,gdldle->range[0]+1);
#endif
                                                                                                                                    /* +1 because the 1st byte has the offset 1 instead of 0*/
                                                      *lfile=g_string_new(gdldle->temp_local_fname->str);
                                                      *start_pos=gdldle->range[0];
                                                      update_gdl_cmd_of_this_gdle(gdle);        /* reupdate the cmd */
                                                      break;
                                                }
                                                else
                                                {
                                                      gdldle->is_started=0;
                                                      gdldle->is_running=0;
                                                      gdldle->last_start_time=time(NULL)+360;   /* wait a lot longer */
                                                }
                                          }
                                    }
                              }
                        }
                        break;
                  }
            }

            if(ret==-1)
            {
                  /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
                  for(i=0;i<gdl_array->len;i++)
                  {
                        gdle=g_ptr_array_index(gdl_array,i);
                        if(gdle->gdl_id==gdl_id)
                        {
                              if(gdle->gdl_links!=NULL)
                              {
                                    for(j=0;j<gdle->gdl_links->len;j++)
                                    {
                                          gdldle=g_ptr_array_index(gdle->gdl_links,j);
      
                                          disp_msg(DEBUG_MSG,"do_gdl_start","|lu",gdl_id,"|d",j,"|d",(int)(gdldle->is_started),NULL);
                                          if(gdldle->is_started==0)           /* we can only start a not yet started function */
                                                                                                      /* now, we can take even waiting xfer */
                                          {
                                                if(!strcmp(gdldle->nickname->str,nickname))
                                                {
                                                      if(allocate_range(gdldle,gdle))
                                                      {
                                                            gdldle->is_running=1;               /* mark this transfer as started */
                                                            gdldle->is_started=1;
                                                            gdldle->thread_id=thread_id;
                                                            ret=0;
            
                                                            *str=g_string_new("");
#ifndef __USE_FILE_OFFSET64
                                                            g_string_sprintf(*str,"$Get %s$%lu",gdldle->remote_fname->str,gdldle->range[0]+1);
#else
                                                            g_string_sprintf(*str,"$Get %s$%llu",gdldle->remote_fname->str,gdldle->range[0]+1);
#endif
                                                                                                                                          /* +1 because the 1st byte has the offset 1 instead of 0*/
                                                            *lfile=g_string_new(gdldle->temp_local_fname->str);
                                                            update_gdl_cmd_of_this_gdle(gdle);        /* reupdate the cmd */
                                                            break;
                                                      }
                                                      else
                                                      {
                                                            gdldle->is_started=0;
                                                            gdldle->is_running=0;
                                                            gdldle->last_start_time=time(NULL)+360;   /* wait a lot longuer */
                                                      }
                                                }
                                          }
                                    }
                              }
                              break;
                        }
                  }
            }
      }
      G_UNLOCK(gdl_array);
      if(ret==0)
            disp_msg(DEBUG_MSG,"do_gdl_start","|lu",gdl_id,nickname,"|lu",(unsigned long)thread_id,(*str)->str,(*lfile)->str,NULL);
      else
      {
            disp_msg(DEBUG_MSG,"do_gdl_start","|lu",gdl_id,nickname,"|lu",(unsigned long)thread_id,"fail to find something to do",NULL);

            /* maybe everything is over */
            check_if_dl_complete(gdle);
      }

      return ret;
}

/******************************************************************************/
/* when a download ends, the GDL_ENTRY must be update using GDL_DL_ENTRY data */
/* to take into account downloaded data.                                      */
/******************************************************************************/
/* NOTE: this function must be called when gdldle->is_started==1 */
/*****************************************************************/
static void gdl_post_download_update(GDL_ENTRY *gdle, GDL_DL_ENTRY *gdldle)
{
      RANGE_ENTRY nw;
      int i;

      if(gdldle->cur_dled==0)
            return;                                   /* nothing downloaded */

      nw.range[0]=gdldle->range[0];
      nw.range[1]=gdldle->range[0]+gdldle->cur_dled;
      nw.temp_local_fname=g_string_new(gdldle->temp_local_fname->str);
      
      if(gdle->dld_ranges->len==0)
      {
            gdle->dld_ranges=g_array_append_val(gdle->dld_ranges,nw);
      }
      else
      {     /* insert inside dld_ranges but keep it ordered */
            int inserted=0;

            for(i=0;i<gdle->dld_ranges->len;i++)
            {
                  RANGE_ENTRY *p1;
                  p1=&(g_array_index(gdle->dld_ranges,RANGE_ENTRY,i));

                  if(nw.range[0]<p1->range[0])
                  {
                        gdle->dld_ranges=g_array_insert_val(gdle->dld_ranges,i,nw);
                        inserted=1;
                        break;
                  }
            }

            if(!inserted)
            {
                  gdle->dld_ranges=g_array_append_val(gdle->dld_ranges,nw);
            }
      }
      
      /* maybe we have the whole file now ? */
      check_if_dl_complete(gdle);
}

/********************************************************************/
/* this function is called when a GDL download fails                */
/* this works when a request has been made but has never started    */
/* this can occurs either if another xfer is still in progress with */
/* the given user or either because it never replies to request     */
/********************************************************************/
void do_gdl_abort(unsigned int gdl_id, char *nickname)
{
      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            int i;
            GDL_ENTRY *gdle;

            /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
            for(i=0;i<gdl_array->len;i++)
            {
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle->gdl_id==gdl_id)
                  {
                        if(gdle->gdl_links!=NULL)
                        {
                              int j;

                              for(j=0;j<gdle->gdl_links->len;j++)
                              {
                                    GDL_DL_ENTRY *gdldle;

                                    gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                    if(gdldle->is_started==0)           /* we can only start a not yet started function */
                                    {
                                          if(!strcmp(gdldle->nickname->str,nickname))
                                          {
                                                /* ok, we have found GDL_DL_ENTRY */
                                                gdldle->is_running=0;
                                                gdldle->last_start_time=time(NULL); /* to avoid immediat retry, adjust the start time */

                                                break;
                                          }
                                    }
                              }
                        }
                        break;
                  }
            }
      }
      G_UNLOCK(gdl_array);
}


/******************************************************************************/
/* this function is called when a GDL download fails but after a do_gdl_start */
/******************************************************************************/
/* if is_fatal is set, this GDL_DL_ENTRY is discarded */
/******************************************************/
void do_gdl_fail(unsigned int gdl_id, char *nickname, char *local_fname, int is_fatal)
{
      int fnd=0;

#if 0
      if(local_fname!=NULL)
#endif
            disp_msg(DEBUG_MSG,"do_gdl_fail","|lu",gdl_id,nickname,local_fname,NULL);
#if 0
      else
            disp_msg(INFO_MSG,"do_gdl_fail","|lu",gdl_id,nickname,"NULL fname",NULL);
#endif

      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            int i;
            GDL_ENTRY *gdle;

            /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
            for(i=0;i<gdl_array->len;i++)
            {
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle->gdl_id==gdl_id)
                  {
                        if(gdle->gdl_links!=NULL)
                        {
                              int j;

#if 0
                              if(local_fname!=NULL)
                              {
#endif
                                    for(j=0;j<gdle->gdl_links->len;j++)
                                    {
                                          GDL_DL_ENTRY *gdldle;

                                          gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                          if(gdldle->is_started)
                                          {
                                                if( (!strcmp(gdldle->nickname->str,nickname)) &&                                                                        /* same nickname */
                                                      (!strcmp(gdldle->temp_local_fname->str,local_fname)))                                                 /* and same fname */
                                                {
                                                      /* ok, we have found GDL_DL_ENTRY */
                                                      fnd=1;

                                                      /* the download has at least successfully started, some data may have been downloaded */
                                                      gdl_post_download_update(gdle,gdldle);
                                                      gdldle->is_started=0;
                                                      g_string_free(gdldle->temp_local_fname,TRUE);
                                                      gdldle->temp_local_fname=NULL;
      
                                                      gdldle->is_running=0;
                                                      gdldle->last_start_time=time(NULL); /* to avoid immediat retry, adjust the start time */
      
                                                      if(is_fatal)
                                                            unlink_and_free_dl_entry(gdle,j,0);
                                                      break;
                                                }
                                          }
                                    }
#if 0
                              }
                              else
                              {
                                    /* this side is mainly used to fail a GDL entry when it has not yet started a download */
                                    /* (==when the /XDL command fails) */
                                    for(j=0;j<gdle->gdl_links->len;j++)
                                    {
                                          GDL_DL_ENTRY *gdldle;

                                          gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                          if((gdldle->is_running)&&(gdldle->is_started==0))           /* in try mode */
                                          {
                                                if(!strcmp(gdldle->nickname->str,nickname))                                                                       /* same nickname (no filename provided) */
                                                {
                                                      /* ok, we have found GDL_DL_ENTRY */
                                                      fnd=1;

                                                      /* the download has at least successfully started, some data may have been downloaded */
                                                      gdl_post_download_update(gdle,gdldle);
                                                      gdldle->is_started=0;
                                                      g_string_free(gdldle->temp_local_fname,TRUE);
                                                      gdldle->temp_local_fname=NULL;
      
                                                      gdldle->is_running=0;
                                                      gdldle->last_start_time=time(NULL); /* to avoid immediat retry, adjust the start time */
      
                                                      if(is_fatal)
                                                            unlink_and_free_dl_entry(gdle,j,0);
                                                      break;
                                                }
                                          }
                                    }
                              }
#endif
                        }
                        break;
                  }
            }
      }
      G_UNLOCK(gdl_array);
      if(!fnd)
      {
#if 0
            if(local_fname!=NULL)
#endif
                  disp_msg(ERR_MSG,"do_gdl_fail","|lu",gdl_id,nickname,local_fname,NULL);
#if 0
            else
                  disp_msg(INFO_MSG,"do_gdl_fail","|lu",gdl_id,nickname,"NULL fname",NULL);
#endif
      }
}

/*****************************************************************/
/* this function is called when a GDL download successfully ends */
/* if the remote client is standard, there is no way to start another */
/* segment, this entry must released because the thread will die */
/*****************************************************************/
void do_gdl_success(unsigned int gdl_id, pthread_t ptid, int with_end)
{
      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            int i;
            GDL_ENTRY *gdle;

            /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
            for(i=0;i<gdl_array->len;i++)
            {
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle->gdl_id==gdl_id)
                  {
                        if(gdle->gdl_links!=NULL)
                        {
                              int j;

                              for(j=0;j<gdle->gdl_links->len;j++)
                              {
                                    GDL_DL_ENTRY *gdldle;

                                    gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                    if((gdldle->is_started==1)&&(gdldle->thread_id==ptid))
                                    {
                                          /* ok, we have found GDL_DL_ENTRY */

                                          /* the download has at least successfully started, some data may have been downloaded */
                                          gdl_post_download_update(gdle,gdldle);
                                          g_string_free(gdldle->temp_local_fname,TRUE);
                                          gdldle->temp_local_fname=NULL;
                                          gdldle->is_started=0;
                                          if(with_end)
                                          {
                                                gdldle->is_running=0;
                                                gdldle->last_start_time=time(NULL); /* to avoid immediat retry, adjust the start time */
                                          }
                                          break;
                                    }
                              }
                        }
                        break;
                  }
            }
      }
      G_UNLOCK(gdl_array);
}

/***********************************************************************************/
/* update the number of bytes downloaded since the beginning of the DL_ENTRY range */
/* at the same time, compute the number of bytes we can download (max: 8192 bytes) */
/* when nothing can be downloaded, return 0                                        */
/***********************************************************************************/
unsigned int gdl_get_amount(unsigned int gdl_id, pthread_t ptid, unsigned long total_pos)
{
      unsigned int ret=0;
      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            int i;
            GDL_ENTRY *gdle;

            /* search and update the GDL_DL_ENTRY of a GDL_ENTRY */
            for(i=0;i<gdl_array->len;i++)
            {
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle->gdl_id==gdl_id)
                  {
                        if(gdle->gdl_links!=NULL)
                        {
                              int j;

                              for(j=0;j<gdle->gdl_links->len;j++)
                              {
                                    GDL_DL_ENTRY *gdldle;

                                    gdldle=g_ptr_array_index(gdle->gdl_links,j);

                                    if((gdldle->is_started==1)&&(gdldle->thread_id==ptid))
                                    {
                                          GArray *dl_ar;

                                          /* ok, we have found GDL_DL_ENTRY */
                                          gdle->instant_spd+= (total_pos-gdldle->cur_dled);

                                          gdldle->cur_dled=total_pos;

                                          /* compute the number of bytes we still can download */
                                          dl_ar=ranges_to_planar(gdle,0);
                                          if(dl_ar!=NULL)
                                          {
                                                int k;
                                                unsigned long cur_pos=gdldle->range[0]+total_pos;

                                                k=0;
                                                while(k<dl_ar->len)
                                                {
                                                      S_RNG *cur;
                                                      cur=&(g_array_index(dl_ar,S_RNG,k));
                                                      if(cur->e==cur_pos)
                                                      {
                                                            /* we have found our entry */
                                                            /* 2 cases: k==(dl_ar->len-1) [we are the last segment or not] */
                                                            if(k==dl_ar->len-1)
                                                            {     /* we are the last segment */
                                                                  /* the biggest downloadable size is: gdldle->remote_fsize-cur_pos */
                                                                  ret=gdldle->remote_fsize-cur_pos;
                                                            }
                                                            else
                                                            {
                                                                  /* not the last segment, so the biggest size is the number of bytes until the beginning of the next segment */
                                                                  S_RNG *nxt;
                                                                  nxt=&(g_array_index(dl_ar,S_RNG,k+1));    /* next segment */

                                                                  /* we take the block size but we also take into account our own filesize */
                                                                  ret=MIN(nxt->b,gdldle->remote_fsize)-cur_pos;
                                                            }

                                                            /* the biggest size is 8KB */
                                                            if(ret>8192)
                                                                  ret=8192;
                                                            break;
                                                      }
                                                      k++;
                                                }
                                                /* if we don't found ourselves, this means our block has reached the next one, so nothing can be download */
                                                g_array_free(dl_ar,TRUE);
                                          }
                                          break;
                                    }
                              }
                        }
                        break;
                  }
            }
      }
      G_UNLOCK(gdl_array);
      return ret;
}

/******************************/
/* display the GDL quick list */
/******************************/
void do_gdl_qlst(int sck)
{
      int i;

      disp_msg(GDL_QLST_BEGIN,"",NULL);
      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            for(i=0;i<gdl_array->len;i++)
            {
                  GDL_ENTRY *gdle;
                  
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle!=NULL)
                  {
                        disp_msg(GDL_QLST_ENTRY,"","|lu",(unsigned long)(gdle->gdl_id),
                                                                              gdle->local_fname->str,
                                                                              "|lu",(unsigned long)(gdle->total_size),
                                                                              NULL);
                  }
            }
      }
      G_UNLOCK(gdl_array);
      disp_msg(GDL_QLST_END,"",NULL);
}

static GString *build_gdl_links_str(GPtrArray *gdl_dl_entries,unsigned long *dld_bytes)
{
      GString *nw;
      int i;

      nw=g_string_new("");
      if(gdl_dl_entries!=NULL)
      {
            for(i=0;i<gdl_dl_entries->len;i++)
            {
                  GDL_DL_ENTRY *ptr;

                  ptr=g_ptr_array_index(gdl_dl_entries,i);
                  if(ptr!=NULL)
                  {
#ifndef __USE_FILE_OFFSET64
                        g_string_sprintfa(nw,"%s$%s$%lu$",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#else
                        g_string_sprintfa(nw,"%s$%s$%llu$",ptr->nickname->str,ptr->remote_fname->str,ptr->remote_fsize);
#endif

                        if(ptr->is_running==0)
                        {
                              nw=g_string_append_c(nw,'W');       /* WAITING */
                        }
                        else
                        {
                              if(ptr->is_started==0)
                              {
                                    nw=g_string_append_c(nw,'T');       /* TRYING */
                              }
                              else
                              {
#ifndef __USE_FILE_OFFSET64
                                    g_string_sprintfa(nw,"R%lu[%lu;%lu]",ptr->cur_dled,ptr->range[0],ptr->range[1]);
#else
                                    g_string_sprintfa(nw,"R%llu[%llu;%llu]",ptr->cur_dled,ptr->range[0],ptr->range[1]);
#endif
                                    (*dld_bytes)+=ptr->cur_dled;
                              }
                        }
                  }
                  nw=g_string_append_c(nw,'|');       /* SEPARATOR */
            }
      }
      return nw;
}

static GString *build_gdl_range_str(GArray *dld_ranges, unsigned long *dld_bytes)
{
      GString *nw;
      int i;

      nw=g_string_new("");
      if(dld_ranges!=NULL)
      {
            for(i=0;i<dld_ranges->len;i++)
            {
                  RANGE_ENTRY *ptr;

                  ptr=&(g_array_index(dld_ranges,RANGE_ENTRY,i));
                  if(ptr!=NULL)
                  {
#ifndef __USE_FILE_OFFSET64
                        g_string_sprintfa(nw,"%s$[%lu;%lu]",ptr->temp_local_fname->str,ptr->range[0],ptr->range[1]);
#else
                        g_string_sprintfa(nw,"%s$[%llu;%llu]",ptr->temp_local_fname->str,ptr->range[0],ptr->range[1]);
#endif
                        (*dld_bytes)+=(ptr->range[1]-ptr->range[0]);
                  }
                  nw=g_string_append_c(nw,'|');       /* SEPARATOR */
            }
      }
      return nw;
}

static GString *build_gdl_autoscan_str(GArray *autoscan)
{
      GString *nw;
      int i;

      nw=g_string_new("");
      if(autoscan!=NULL)
      {
            for(i=0;i<autoscan->len;i++)
            {
                  GDL_AS_ENTRY *ptr;

                  ptr=&(g_array_index(autoscan,GDL_AS_ENTRY,i));
                  if(ptr!=NULL)
                  {
                        g_string_sprintfa(nw,"%lu$%s",ptr->gae_id,ptr->search_pattern);
                  }
                  nw=g_string_append_c(nw,'|');       /* SEPARATOR */
            }
      }
      return nw;
}

/*****************************/
/* display the GDL long list */
/*****************************/
void do_gdl_lst(int sck)
{
      int i;

      disp_msg(GDL_LST_BEGIN,"",NULL);
      G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            for(i=0;i<gdl_array->len;i++)
            {
                  GDL_ENTRY *gdle;
                  unsigned long dld_size=0;
                  
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle!=NULL)
                  {
                        GString *end_script_and_crc;

                        GString *links_str,*range_str, *ascan_str;
                        links_str=build_gdl_links_str(gdle->gdl_links,&dld_size);
                        range_str=build_gdl_range_str(gdle->dld_ranges,&dld_size);
                        ascan_str=build_gdl_autoscan_str(gdle->autoscan);

                        /* script to start at end */
                        end_script_and_crc=g_string_new((gdle->at_end_script)?gdle->at_end_script->str:"");

                        if(gdle->has_crc)
                        {
                              g_string_append(end_script_and_crc," &CRC:");
                              append_MD4_to_str(end_script_and_crc,gdle->ed2k_crc);

                              if(gdle->ed2k_partial_crc)
                              {
                                    g_string_append(end_script_and_crc," &L0CRC");
                              }
                        }

                        disp_msg(GDL_LST_ENTRY,"","|lu",(unsigned long)(gdle->gdl_id),
                                                                              gdle->local_fname->str,
                                                                              "|lu",(unsigned long)(gdle->total_size),
                                                                              "|lu",(unsigned long)(gdle->dl_offset),         /* byte here before attachement */
                                                                              "|lu",(unsigned long)(dld_size),                            /* amount of bytes still here */
                                                                              "|lu",(unsigned long)(gdle->start_time),        /* creation/attachement time */
                                                                              "|lu",(unsigned long)(gdle->cur_spd),                 /* number of bytes received in the previous 10 seconds range */
                                                                              "|s",(gdle->post_dir)?gdle->post_dir->str:"",         /* directory rename */
                                                                              "|s",(gdle->post_fname)?gdle->post_fname->str:"",     /* filename rename */
                                                                              "|s",end_script_and_crc->str, /* script to start at end + CRC */
                                                                              links_str->str,
                                                                              range_str->str,
                                                                              ascan_str->str,
                                                                              NULL);

                        g_string_free(end_script_and_crc,TRUE);

                        g_string_free(links_str,TRUE);
                        g_string_free(range_str,TRUE);
                        g_string_free(ascan_str,TRUE);
                  }
            }
      }
      G_UNLOCK(gdl_array);

      /* include broken GDL has GDL entry */
      {
            GList *le;
            GString *info;
            time_t cur_time=time(NULL);

            info=g_string_new("");

            G_LOCK(broken_gdl_array);
            le=g_list_first(broken_gdl_array);
            while(le!=NULL)
            {
                  BROKEN_GDL_ENTRY *bge;
      
                  bge=le->data;

                  g_string_assign(info,"Broken file. ");
                  if(bge->ed2k_partial_crc==NULL)
                  {
                        int nx_atmp=MIN_DELAY_BETWEEN_2_ED2K_CRC_QUERY-(cur_time-bge->last_attempt);
                        if(nx_atmp<=0)
                              g_string_append(info,"L0CRC not available. Retrieve attempt in progress");
                        else
                              g_string_sprintfa(info,"L0CRC not available. Next retrieve attempt in %d seconds",nx_atmp);
                  }
                  else
                  {
                        int nx_atmp=MIN_DELAY_BETWEEN_2_ED2K_CRC_QUERY-(cur_time-bge->last_attempt);
                        if(nx_atmp<=0)
                              g_string_append(info,"L0CRC ok. reparing process in progress");
                        else
                              g_string_sprintfa(info,"L0CRC ok. reparing process will start in %d seconds",nx_atmp);
                  }

                  disp_msg(GDL_LST_ENTRY,"","|lu",(unsigned long)(bge->broken_gdl_id),
                                                                              bge->local_fname->str,
                                                                              "|lu",(unsigned long)(bge->total_size),
                                                                              "|lu",(unsigned long)0,       /* byte here before attachement */
                                                                              "|lu",(unsigned long)0,       /* amount of bytes still here */
                                                                              "|lu",(unsigned long)0,       /* creation/attachement time */
                                                                              "|lu",(unsigned long)0,       /* number of bytes received in the previous 10 seconds range */
                                                                              "|s","",    /* directory rename */
                                                                              "|s","",    /* filename rename */
                                                                              "|s",info->str,   /* script to start at end + CRC */
                                                                              "",
                                                                              "",
                                                                              "",
                                                                              NULL);


                  le=g_list_next(le);
            }
            G_UNLOCK(broken_gdl_array);

            g_string_free(info,TRUE);
      }

      disp_msg(GDL_LST_END,"",NULL);
}

/*********************************************************************************************************/
/* modify the timeout of waiting GDL sources having the given nickname to force them to retry immediatly */
/*********************************************************************************************************/
void gdl_wake_up_sources(char *nickname, int without_lock)
{
      int i;
      time_t cur_time=time(NULL);

      if(without_lock==0)
            G_LOCK(gdl_array);
      if(gdl_array!=NULL)
      {
            for(i=0;i<gdl_array->len;i++)
            {
                  GDL_ENTRY *gdle;
                  
                  gdle=g_ptr_array_index(gdl_array,i);
                  if(gdle!=NULL)
                  {
                        GPtrArray *gdl_dl_entries;

                        gdl_dl_entries=gdle->gdl_links;
                        if(gdl_dl_entries!=NULL)
                        {
                              int j;

                              for(j=0;j<gdl_dl_entries->len;j++)
                              {
                                    GDL_DL_ENTRY *ptr;

                                    ptr=g_ptr_array_index(gdl_dl_entries,j);
                                    if((ptr!=NULL)&&(ptr->is_running==0)&&(!strcmp(ptr->nickname->str,nickname)))
                                    {
                                          /* here is an entry in waiting status and having the wanted nickname */
                                          /* try to start 1 second later */
                                          ptr->last_start_time=cur_time-(MAX_WAIT_TIME-1);
                                    }
                              }
                        }
                  }
            }
      }
      if(without_lock==0)
            G_UNLOCK(gdl_array);
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* ------------------ GDL with erroneous CRC support ------------------------ */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
static void free_broken_gdl_mem(BROKEN_GDL_ENTRY *bge)
{
      if(bge->local_fname)
            g_string_free(bge->local_fname,TRUE);
      if(bge->ed2k_partial_crc)
            free(bge->ed2k_partial_crc);
      if(bge->locked_crc_file_fd!=-1)
            close(bge->locked_crc_file_fd);
      free(bge);
}

/****************************************************************/
/* try to detach a broken GDL                                   */
/* if with_deletion is set, the broken GDL directory is deleted */
/****************************************************************/
/* output: 0=success, !=0=error */
/********************************/
static int do_broken_gdl_detach(unsigned int gdl_id, int with_deletion)
{
      GList *le;
      int ret=1;  /* by default: error */
      GString *bge_dir=NULL;

      G_LOCK(broken_gdl_array);
      le=g_list_first(broken_gdl_array);
      while(le!=NULL)
      {
            BROKEN_GDL_ENTRY *bge;

            bge=le->data;
            if(bge->broken_gdl_id==gdl_id)
            {
                  broken_gdl_array=g_list_remove(broken_gdl_array,bge);
                  if(with_deletion==TRUE)
                  {
                        /* keep the filename for deletion ... later */
                        bge_dir=g_string_new("GDL/broken$");
                        g_string_append(bge_dir,bge->local_fname->str);
                  }
                  free_broken_gdl_mem(bge);
                  break;
            }
            le=g_list_next(le);
      }
      G_UNLOCK(broken_gdl_array);

      if(bge_dir!=NULL)
      {
            /* delete "GDL/broken$".bge->local_fname->str directory */
            recur_del(bge_dir);
            g_string_free(bge_dir,TRUE);
      }

      return ret;
}

static void touch_file(char *filename)
{
      int fd;

      fd=open(filename,O_RDWR|O_CREAT,0777);
      if(fd!=-1)
            close(fd);
      else
            perror("touch_file: open fail");
}

/****************************************************************************/
/* extract one or more blocs of the corrupted file and put them into a file */
/* also update the .cmd file by adding a R row.                             */
/*********************************************************************************/
/* input: fd_cmd: fd on the .cmd file, already at the right position in the file */
/*        adr: corrupted file mapped into memory.                                */
/*        total_size: size of the corrupted file.                                */
/*        start_idx: first bloc to write (position= start_idx*PARTSIZE)          */
/*        end_idx: first bloc not to write (position= end_idx*PARTSIZE)          */
/*                 if==UINT_MAX, this means the end of the file                  */
/*        base_wd= "GDL/broken$filename" (current working directory)             */
/*        ok_dir_name= "GDL/filename" (name of the current working directory if  */
/*                     the resplit function succeded                             */
/*********************************************************************************/
/* output: 0=ok, !=0=error */
/***************************/
static int write_gdl_bloc(int fd_cmd, guint8 *adr, off_t total_size, unsigned int start_idx, 
                          unsigned int end_idx, const gchar *base_wd, const gchar *ok_dir_name)
{
      off_t begin_pos, end_pos;
      off_t bk_size; 
      GString *fragname;
      int frag_fd;
      GString *path;

#if 0
      printf("si: %u ei: %u (%u)\n",start_idx,end_idx,UINT_MAX);
#endif
      begin_pos=start_idx*PARTSIZE;
      if(end_idx==UINT_MAX)
            end_pos=total_size;
      else
            end_pos=end_idx*PARTSIZE;

      /* this case occurs when the last segment is corrupted */
      if(begin_pos>=end_pos)
            return 0;

      fragname=g_string_new("");
#ifndef __USE_FILE_OFFSET64
      g_string_sprintf(fragname,"%08lX-%08lX",begin_pos,end_pos);
#else
      g_string_sprintf(fragname,"%016llX-%016llX",begin_pos,end_pos);
#endif
      path=g_string_new(base_wd);
      g_string_append_c(path,'/');
      g_string_append(path,fragname->str);

      frag_fd=open(path->str,O_WRONLY|O_CREAT|O_TRUNC,0777);
      if(frag_fd==-1)
      {
            disp_msg(ERR_MSG,"write_gdl_bloc","unable to create file","|s",path->str,"because:","|s",strerror(errno),NULL);
            g_string_free(fragname,TRUE);
            g_string_free(path,TRUE);
            return 1;         /* abort on error */
      }

      bk_size=end_pos-begin_pos;
#if 0
      printf("sp: %lu ep: %lu (%ld)\n",begin_pos,bk_size,end_pos);
#endif
      if(write(frag_fd,adr+begin_pos,bk_size)!=bk_size)
      {
            disp_msg(ERR_MSG,"write_gdl_bloc","fragment write fail on file","|s",path->str,"because:","|s",strerror(errno),NULL);
            close(frag_fd);
            g_string_free(fragname,TRUE);
            g_string_free(path,TRUE);
            return 1;         /* abort on error */
      }
      close(frag_fd);

      /* the fragment write was successful, its time to append the fragment range to .cmd */
#ifndef __USE_FILE_OFFSET64
      g_string_sprintf(path,"R|%s/%s|%lu|%lu\n",ok_dir_name,fragname->str,begin_pos,end_pos);
#else
      g_string_sprintf(path,"R|%s/%s|%llu|%llu\n",ok_dir_name,fragname->str,begin_pos,end_pos);
#endif
      if(write(fd_cmd,path->str,path->len)!=path->len)
      {
            disp_msg(ERR_MSG,"write_gdl_bloc","fail to update .cmd of","|s",base_wd,"because:","|s",strerror(errno),NULL);
            g_string_free(fragname,TRUE);
            g_string_free(path,TRUE);
            return 1;         /* abort on error */
      }

      /* success... fragment created and .cmd updated */
      g_string_free(fragname,TRUE);
      g_string_free(path,TRUE);
      return 0;
}

/************************************************************************/
/* search for an entry with the content "R|GDL/filename/filename|0|..." */
/* and replace the 'R' by '#'.                                          */
/************************************************************************/
static void cmd_alter_global_file(int fd_cmd,BROKEN_GDL_ENTRY *bge)
{
      GString *str;
      struct stat st;
      guint8 *adr;

      if(fstat(fd_cmd,&st)!=0)
      {
            disp_msg(ERR_MSG,"cmd_alter_global_file","unable to stat .cmd file of GDL","|s",bge->local_fname->str,"because:","|s",strerror(errno),NULL);
            return;
      }

      str=g_string_new("");
      g_string_sprintf(str,"R|GDL/%s/%s|0|",bge->local_fname->str,bge->local_fname->str);

      adr=mmap(NULL,st.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_cmd,0);
      if(adr!=MAP_FAILED)
      {
            int i;
            int ln;

            ln=st.st_size-str->len;

            for(i=0;i<ln;i++)
            {
                  if(!strncmp(adr+i,str->str,str->len))
                  {
                        adr[i]='#';
#if 0
                        printf("code found\n");
#endif
                        break;
                  }
            }

            munmap(adr,st.st_size);
      }
      else
      {
            disp_msg(ERR_MSG,"cmd_alter_global_file","unable to map .cmd file of GDL","|s",bge->local_fname->str,"because:","|s",strerror(errno),NULL);
      }

      g_string_free(str,TRUE);
}

/********************************************************************************************/
/* split the file defined by bge into one or more files without error (dirty_bloc contains  */
/* the index (unsigned int) of erroneous blocs. adr is the erroneous file mapped in memory. */
/********************************************************************************************/
/* on success: bge is converted into a valid GDL and reattached, NULL is returned */
/*             on error, bge is left untouched                                    */
/**********************************************************************************/
/* note: computed_crc_len is in number of MD4 blocs, not in bytes */
/******************************************************************/
static BROKEN_GDL_ENTRY *resplit_broken_gdl(BROKEN_GDL_ENTRY *bge, GArray *dirty_bloc,guint8 *adr, unsigned int computed_crc_len)
{
      gchar *base_wd;
      gchar *crc_fname;
      gchar *cmd_fname;
      gchar *lock_fname;
      gchar *faulty_fname;
      gchar *ok_dir_name;
      gchar *dup_local_fname;
      unsigned int start_idx, end_idx,cur_pos,i;

      base_wd=g_strconcat("GDL/broken$",bge->local_fname->str,NULL);
      crc_fname=g_strconcat(base_wd,"/.crc",NULL);
      cmd_fname=g_strconcat(base_wd,"/.cmd",NULL);
      lock_fname=g_strconcat(base_wd,"/.lock",NULL);
      faulty_fname=g_strconcat(base_wd,"/",bge->local_fname->str,NULL); /* looks weird but it is correct, the fname w/path is "GDL/broken$fname/fname" */
      ok_dir_name=g_strconcat("GDL/",bge->local_fname->str,NULL);
      dup_local_fname=g_strdup(bge->local_fname->str);
      
      if(dirty_bloc->len==computed_crc_len)
      {
            /* and not only one or 2 faulty blocs, all blocs are erroneous */
            /* it is the easiest problem */
            /* we have a directory named broken$filename, a file named .cmd (valid for both broken and valid GDL) and a file named .crc */
            /* we have to 1) unlink the faulty file 2) unlike the .crc 3) create the .lock 4) rename the directory */
            /* destroy the BROKEN_GDL_ENTRY and call do_gdl_attach to reattach the newly created GDL */
            unlink(faulty_fname);
            unlink(crc_fname);
            touch_file(lock_fname);
            rename(base_wd,ok_dir_name);  /* if a rename fails here, the GDL will be probably lost */
            free_broken_gdl_mem(bge);
            bge=NULL;
            do_gdl_attach(dup_local_fname);
      }
      else
      {
            int fd_cmd;
            off_t initial_end_position;

            fd_cmd=open(cmd_fname,O_RDWR);
            if(fd_cmd==-1)
            {
                  disp_msg(ERR_MSG,"resplit_broken_gdl","unable to open file","|s",cmd_fname,"because:","|s",strerror(errno),NULL);
                  goto abrt;
            }
            initial_end_position=lseek(fd_cmd,0,SEEK_END);        /* move to the end of the file */
            if(initial_end_position==-1)
            {
                  disp_msg(ERR_MSG,"resplit_broken_gdl","unable to seek to end of file","|s",cmd_fname,"because:","|s",strerror(errno),NULL);
                  close(fd_cmd);
                  goto abrt;
            }

            i=0;
            cur_pos=g_array_index(dirty_bloc,unsigned int,i);
#if 0
            {
                  int aaa;
                  printf("dirty blocs: %d\n",dirty_bloc->len);
                  for(aaa=0;aaa<dirty_bloc->len;aaa++)
                  {
                        printf("%u,",g_array_index(dirty_bloc,unsigned int,aaa));
                  }
                  printf("\n");
            }
#endif

            if(cur_pos!=0)
            {
                  start_idx=0;
                  end_idx=cur_pos;
                  if(write_gdl_bloc(fd_cmd,adr,bge->total_size,start_idx,end_idx,base_wd,ok_dir_name))
                  {
                        /* the write has failed */
                        /* restore .cmd original size */
                        ftruncate(fd_cmd,initial_end_position);
                        close(fd_cmd);
                        goto abrt;
                  }
            }
            start_idx=cur_pos+1;          /* move just after the last known invalid bloc */
            i++;
            while(i<dirty_bloc->len)
            {
                  cur_pos=g_array_index(dirty_bloc,unsigned int,i);
                  if(cur_pos!=start_idx)
                  {
                        end_idx=cur_pos;
                        if(write_gdl_bloc(fd_cmd,adr,bge->total_size,start_idx,end_idx,base_wd,ok_dir_name))
                        {
                              /* the write has failed */
                              /* restore .cmd original size */
                              ftruncate(fd_cmd,initial_end_position);
                              close(fd_cmd);
                              goto abrt;
                        }
                  }
                  start_idx=cur_pos+1;
                  i++;
            }

            /* we have reached the last index, don't forget the last received bloc (at the end (start_idx+1->end of file) */
            end_idx=UINT_MAX;
            if(write_gdl_bloc(fd_cmd,adr,bge->total_size,start_idx,end_idx,base_wd,ok_dir_name))
            {
                  /* the write has failed */
                  /* restore .cmd original size */
                  ftruncate(fd_cmd,initial_end_position);
                  close(fd_cmd);
                  goto abrt;
            }

            /* incredible, all the copy has succeded */

            /* before the end, remove the corrupted full file from the .cmd */
            /* this must be the last task because we alter the pointer */
            cmd_alter_global_file(fd_cmd,bge);

            close(fd_cmd);          /* .cmd is fully valid, all segments was created */

            /* we have to 1) unlink the faulty file 2) unlike the .crc 3) create the .lock 4) rename the directory */
            /* destroy the BROKEN_GDL_ENTRY and call do_gdl_attach to reattach the newly created GDL */
            unlink(faulty_fname);
            unlink(crc_fname);
            touch_file(lock_fname);
            rename(base_wd,ok_dir_name);  /* if a rename fails here, the GDL will be probably lost */
            free_broken_gdl_mem(bge);
            bge=NULL;
            do_gdl_attach(dup_local_fname);
      }

      abrt:
      g_free(dup_local_fname);
      g_free(ok_dir_name);
      g_free(faulty_fname);
      g_free(lock_fname);
      g_free(cmd_fname);
      g_free(crc_fname);
      g_free(base_wd);

      return bge;
}

/********************************************************************************/
/* try to convert the given broken GDL into a valid one (discard invalid blocs) */
/********************************************************************************/
/* output: on success, the bge is free and NULL is returned */
/*         on error, the bge is returned for requeueing     */
/************************************************************/
static BROKEN_GDL_ENTRY *try_to_rebuild_gdl_from_broken_gdl(BROKEN_GDL_ENTRY *bge)
{
      guint8 *adr;
      guint8 *computed_crc;
      unsigned int computed_crc_len;
      int fd;
      GArray *dirty_bloc=NULL;      /* array of unsigned int being partial CRC index */
      unsigned int i;
      gchar *realname;

      realname=g_strconcat("GDL/broken$",bge->local_fname->str,"/",bge->local_fname->str,NULL);
      fd=open(realname,O_RDONLY);
      if(fd==-1)
      {
            disp_msg(ERR_MSG,"try_to_rebuild_gdl_from_broken_gdl","unable to open file","|s",realname,"because:","|s",strerror(errno),NULL);
            g_free(realname);
            return bge;
      }

      /* map the file in the memory */
      adr=mmap(NULL,bge->total_size,PROT_READ,MAP_SHARED,fd,0);
      if(adr==MAP_FAILED)
      {
            disp_msg(ERR_MSG,"try_to_rebuild_gdl_from_broken_gdl","unable to map file","|s",realname,"because:","|s",strerror(errno),NULL);
            close(fd);
            g_free(realname);
            return bge;
      }
      close(fd);

      computed_crc=l0_crc(adr,bge->total_size,&computed_crc_len);
      computed_crc_len/=MD4_DIGEST_LENGTH;            /* we work in number of MD4, not in bytes */
      /* because l0_crc and broken_gdl compute the crc size in the same manner, computed_crc and bge->ed2k_partial_crc */
      /* always have the same size */

      /* compare all CRC and save the CRC index of invalid bloc */
      dirty_bloc=g_array_new(FALSE,FALSE,sizeof(i));
      if(bge->total_size<=PARTSIZE)
      {
            i=0;
            /* the file has a size smaller than 1 bloc thus the L0CRC has a length of exactly 1 */
            /* if it is corrupted, we automatically know which bloc is corrupted */
            g_array_append_val(dirty_bloc,i);
      }
      else
      {
            for(i=0;i<computed_crc_len;i++)
            {
                  if(memcmp(computed_crc+MD4_DIGEST_LENGTH*i,bge->ed2k_partial_crc+MD4_DIGEST_LENGTH*i,MD4_DIGEST_LENGTH))
                  {
                        g_array_append_val(dirty_bloc,i);
                  }
            }
      }
      
      if(dirty_bloc->len==0)  
      {
            g_array_free(dirty_bloc,TRUE);
            disp_msg(ERR_MSG,"try_to_rebuild_gdl_from_broken_gdl","invalid global CRC but valid partial CRC... impossible case for",
                             "|s",realname,". GDL restauration aborted. Keeping broken file",NULL);
            munmap(adr,bge->total_size);
            free_broken_gdl_mem(bge);
            g_free(realname);
            return NULL;      /* impossible restauration, ignore this file */
      }
      else 
      {
            off_t ttl_sz=bge->total_size;

            /* shit, now, it is sure, there is broken blocs :( */
            bge=resplit_broken_gdl(bge,dirty_bloc,adr,computed_crc_len);

            /* here, the original file can have been deleted (the mmap still exists) */
            /* on success, bge=NULL */

            g_array_free(dirty_bloc,TRUE);
            munmap(adr,ttl_sz);
      }
      g_free(realname);
      return bge;
}

/**********************************************************/
/* scans broken_gdl_array every minutes and perform tasks */
/**********************************************************/
static void broken_gdl_thread(void *dummy)
{
      GList *le;

      while(1)
      {
            G_LOCK(broken_gdl_array);
            le=g_list_first(broken_gdl_array);
            while(le!=NULL)
            {
                  BROKEN_GDL_ENTRY *bge;

                  bge=le->data;
                  if((bge->ed2k_partial_crc!=NULL)||(bge->total_size<=PARTSIZE))
                  {
                        broken_gdl_array=g_list_remove(broken_gdl_array,bge);
                        G_UNLOCK(broken_gdl_array);

                        bge=try_to_rebuild_gdl_from_broken_gdl(bge);
                        if(bge!=NULL)
                        {
                              /* reconstruction has failed, requeue the broken GDL at the end */
                              G_LOCK(broken_gdl_array);
                              broken_gdl_array=g_list_append(broken_gdl_array,bge);
                              G_UNLOCK(broken_gdl_array);
                        }
                        goto reloop;
                  }
                  else
                  {
                        time_t cur_time=time(NULL);

                        if((cur_time-bge->last_attempt)>MIN_DELAY_BETWEEN_2_ED2K_CRC_QUERY)
                        {
                              GString *query;

                              bge->last_attempt=cur_time;
                        
                              query=g_string_new("/MD4GET|");
                              append_MD4_to_str(query,bge->ed2k_crc);
#ifndef __USE_FILE_OFFSET64
                              g_string_sprintfa(query,"|%lu|",bge->total_size);
#else
                              g_string_sprintfa(query,"|%llu|",bge->total_size);
#endif
                              add_new_sim_input(0,query->str);
                              g_string_free(query,TRUE);
                        }
                  }
                  le=g_list_next(le);
            }
            G_UNLOCK(broken_gdl_array);
            reloop:
            sleep(60);
      }
      pthread_exit(NULL);
}

/*******************************************************************/
/* check inside the broken GDL list if a GDL match the given value */
/*******************************************************************/
void incoming_partial_md(const guint8 *g_crc, off_t total_size, const guint8 *partial_crc, guint32 partial_size)
{
      GList *le;

      G_LOCK(broken_gdl_array);
      le=g_list_first(broken_gdl_array);
      while(le!=NULL)
      {
            BROKEN_GDL_ENTRY *bge;

            bge=le->data;
            if(bge->ed2k_partial_crc==NULL)
            {
                  if((bge->total_size==total_size)&&
                     (!memcmp(g_crc,bge->ed2k_crc,MD4_DIGEST_LENGTH)))
                  {
                        /* size and global crc match, we have found what we search for */
                        bge->ed2k_partial_crc=malloc(partial_size);
                        if(bge->ed2k_partial_crc!=NULL)
                        {
                              memcpy(bge->ed2k_partial_crc,partial_crc, partial_size);
                              bge->last_attempt=time(NULL);
                        }
                        break;
                  }
            }
            le=g_list_next(le);
      }
      G_UNLOCK(broken_gdl_array);
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* ---------------------------- MET file support ---------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

/****************************************************************/
/* set the time in minutes between 2 scans of the met directory */
/****************************************************************/
void set_met_scan_interval(int duration)
{
      if(duration<=0)
            duration=10;

      if(duration>60)
            duration=60;

      met_scan_interval=duration;
}

/*******************************************/
/* set the directory containing .met files */
/*******************************************/
void set_met_dir(const char *dirname)
{
      if((dirname==NULL)||(strlen(dirname)==0))
            return;

      if(met_dir==NULL)
            met_dir=g_string_new(dirname);
      else
            g_string_assign(met_dir,dirname);
}

/**************************************/
/* get the current .met scan interval */
/**************************************/
int get_met_scan_interval(void)
{
      return met_scan_interval;
}

/***********************************************/
/* get the current .met dir (must not be free) */
/***********************************************/
const GString *get_met_dir(void)
{
      return met_dir;
}

/********************************************/
/* display GDL met variables in VAR message */
/********************************************/
void disp_met_vars(void)
{
      if((met_dir==NULL)||(met_dir->len==0))
            disp_msg(VAR_MSG,NULL,"gdl_met_dir","",NULL);
      else
            disp_msg(VAR_MSG,NULL,"gdl_met_dir",met_dir->str,NULL);

      disp_msg(VAR_MSG,NULL,"gdl_met_poll","|d",met_scan_interval,NULL);
}

/***********************************************/
/* structure of a .met file loaded into memory */
/***********************************************/
typedef struct
{
      guint8 ed2k_crc[MD4_DIGEST_LENGTH];       /* global crc */
      guint8 *ed2k_partial_crc;
} MET_FILE;

/******************************/
/* load a .met file in memory */
/***************************************/
/* .met file format is quite simple    */
/* 1 byte: 0xE0                        */
/* 4 bytes: file size (=S)             */
/* 16 bytes: global CRC                */
/* 2 bytes: number of partial CRC (=N) */
/*   if N!= (S+PARTSIZE-1)/PARTSIZE    */
/*   then, no partial CRC exists else  */
/*   we have N*16 bytes of partial CRC */
/***************************************/
static void load_met_file(GArray *met_files, const char *met_filename)
{
      int fd;
      MET_FILE mf;
      guint8 buf[64];
      guint32 nb_frag;

      disp_msg(INFO_MSG,"load_met_file","Try to load .met file","|s",met_filename,NULL);
      fd=open(met_filename,O_RDONLY);
      if(fd!=-1)
      {
      disp_msg(INFO_MSG,"load_met_file","1",NULL);
            if(read(fd,buf,23)!=23)
                  goto abrt;

      disp_msg(INFO_MSG,"load_met_file","2",NULL);
            if(buf[0]!=0xE0)        /* no valid first byte */
                  goto abrt;
            
            /* get the number of fragment */
            nb_frag=GET_UAA_GUINT16(buf+21);

      disp_msg(INFO_MSG,"load_met_file","3","|lu",nb_frag,NULL);
            /* have partial CRC ? */
            if( nb_frag == 0 )
                  goto abrt;

            /* the partial CRC exists, first, keep the global CRC */
            memcpy(mf.ed2k_crc,buf+5,MD4_DIGEST_LENGTH);

            mf.ed2k_partial_crc=malloc(nb_frag*MD4_DIGEST_LENGTH);
            if(mf.ed2k_partial_crc==NULL)
                  goto abrt;
      
            /* read the partial CRC */
            if(read(fd,mf.ed2k_partial_crc,nb_frag*MD4_DIGEST_LENGTH)!=(nb_frag*MD4_DIGEST_LENGTH))
                  goto abrt;

      disp_msg(INFO_MSG,"load_met_file","4","|lu",nb_frag,"frag CRCs loaded",NULL);
            g_array_append_val(met_files,mf);

            abrt:
            close(fd);
      }
}

/************************************************************/
/* load all the .met files from the met directory in memory */
/************************************************************/
static void load_met_files(GArray *met_files, const char *met_dir)
{
      DIR *dir;

      dir=opendir(met_dir);
      if(dir!=NULL)
      {
            struct dirent *obj;
            GString *np;
            int org_len;

            np=g_string_new(met_dir);
            g_string_append_c(np,'/');
            org_len=np->len;

            while((obj=readdir(dir))!=NULL)
            {
                  if(strlen(obj->d_name)<4)
                        continue;
                  if(strcmp(obj->d_name+strlen(obj->d_name)-4,".met"))
                        continue;

                  g_string_truncate(np,org_len);
                  g_string_append(np,obj->d_name);

                  load_met_file(met_files,np->str);
            }

            g_string_free(np,TRUE);
            closedir(dir);
      }
}

/**********************************************************************/
/* search inside met_files array a .met entry matching the given gdle */
/**********************************************************************/
static void match_gdl_and_met_crc(GArray *met_files,GDL_ENTRY *gdle)
{
      int i;
      MET_FILE *mf;

      for(i=0;i<met_files->len;i++)
      {
            mf=&(g_array_index(met_files,MET_FILE,i));

            if(!memcmp(mf->ed2k_crc,gdle->ed2k_crc,MD4_DIGEST_LENGTH))
            {
                  /* we have found a matching entry */
                  gdle->nb_partial_crc_seg=(gdle->total_size+PARTSIZE-1)/PARTSIZE;

                  gdle->ed2k_partial_crc=malloc(MD4_DIGEST_LENGTH*gdle->nb_partial_crc_seg);
                  if(gdle->ed2k_partial_crc!=NULL)
                  {
                        memcpy(gdle->ed2k_partial_crc,mf->ed2k_partial_crc,MD4_DIGEST_LENGTH*gdle->nb_partial_crc_seg);
                  }
                  break;
            }
      }
}

/*****************************************************************************/
/* search inside met_files array a .met entry matching the given broken gdle */
/*****************************************************************************/
static void match_broken_gdl_and_met_crc(GArray *met_files,BROKEN_GDL_ENTRY *bge)
{
      int i;
      MET_FILE *mf;
      guint nb_partial_crc_seg;

      disp_msg(INFO_MSG,"match_broken_gdl_and_met_crc","trying to find broken entry",NULL);
      for(i=0;i<met_files->len;i++)
      {
            mf=&(g_array_index(met_files,MET_FILE,i));

            if(!memcmp(mf->ed2k_crc,bge->ed2k_crc,MD4_DIGEST_LENGTH))
            {
                  /* we have found a matching entry */
                  nb_partial_crc_seg=(bge->total_size+PARTSIZE-1)/PARTSIZE;

                  bge->ed2k_partial_crc=malloc(MD4_DIGEST_LENGTH*nb_partial_crc_seg);
                  if(bge->ed2k_partial_crc!=NULL)
                  {
                        memcpy(bge->ed2k_partial_crc,mf->ed2k_partial_crc,MD4_DIGEST_LENGTH*nb_partial_crc_seg);
                        disp_msg(INFO_MSG,"match_broken_gdl_and_met_crc","find a CRC for ","|s",bge->local_fname->str,NULL);
                  }
                  break;
            }
      }
}

/********************************************************************/
/* check the .met directory, the gdl_array and the broken_gdl_array */
/* to find if some GDL has CRC without L0CRC included but existing  */
/* in a .met file.                                                  */
/********************************************************************/
static void scan_met_dir(void)
{
      GArray *met_files;
      int i;

      disp_msg(INFO_MSG,"scan_met_dir","start",NULL);
      if(met_dir==NULL)
            disp_msg(INFO_MSG,"scan_met_dir","no dir",NULL);
      else if(met_dir->len==0)
            disp_msg(INFO_MSG,"scan_met_dir","null dir",NULL);
      else
            disp_msg(INFO_MSG,"scan_met_dir","met dir","|s",met_dir->str,NULL);

      /* have a .met dir ? */
      if((met_dir==NULL)||(met_dir->len==0))
            return;

      met_files=g_array_new(FALSE,FALSE,sizeof(MET_FILE));
      load_met_files(met_files,met_dir->str);
      disp_msg(INFO_MSG,"scan_met_dir","|d",(int)met_files->len,".met files loaded",NULL);
      if(met_files->len)
      {
            /* it's time to check CRC of valid GDL */
            G_LOCK(gdl_array);
            i=0;
            while(i<gdl_array->len)
            {
                  GDL_ENTRY *gdle;
                  gdle=g_ptr_array_index(gdl_array,i);
      
                  if((gdle->has_crc)&&(gdle->ed2k_partial_crc==NULL))
                  {
                        /* only check GDL having a global CRC and without partial CRC */
                        match_gdl_and_met_crc(met_files,gdle);
                  }
                  i++;
            }
            G_UNLOCK(gdl_array);

            /* and also CRC of broken GDL */
            /* Note: a broken GDL always had a global CRC else we cannot know it is broken :) */
            G_LOCK(broken_gdl_array);
            {
                  GList *le;
                  le=g_list_first(broken_gdl_array);
                  while(le!=NULL)
                  {
                        BROKEN_GDL_ENTRY *bge;

                        bge=le->data;
                        if(bge->ed2k_partial_crc==NULL)
                        {
                              /* no partial CRC */
                              match_broken_gdl_and_met_crc(met_files,bge);
                        }
                        le=g_list_next(le);
                  }
            }
            G_UNLOCK(broken_gdl_array);
      }

      for(i=0;i<met_files->len;i++)
      {
            if(g_array_index(met_files,MET_FILE,i).ed2k_partial_crc)
                  free(g_array_index(met_files,MET_FILE,i).ed2k_partial_crc);
      }
      g_array_free(met_files,TRUE);
}


Generated by  Doxygen 1.6.0   Back to index