#include "wind_pk.h" #include "windmisc.h" #include "pckt_prt.h" /* for packet_log(); */ #include #include /* private subroutines: */ packet *store_packet(double time,uint errors,uchar *ds,pklist *list); void add_packet_to_list(packet *pk,pklist *list); void make_elm_array(); void list_to_array(pklist *pkl); uint4 packet_id(uchar *p); uint2 packet_idtype(uchar *p); uint2 packet_dsize(uchar *p); uint2 packet_instseq(uchar *p); uchar packet_seq(uchar *p); uchar packet_inst(uchar *p); void print_packet_summary(FILE *fp); int packet_type_index(uint4 id); int pk_time_compare(const void * p1, const void *p2); boolean_t is_sortable(pklist pkl); int batch_print = 0; FILE *pckt_log_fp; static pklist packet_types[]={ /*id mask sortable_type size */ {"All Unkown Packets" ,INVALID_ID, 0x00000000, B_FALSE, 0 }, /* must be first */ {"Housekeeping" ,HKP_ID, 0xffff0000, B_FALSE, 999}, {"Key Parameters" ,KPD_ID, 0xffff0000, B_FALSE, 999}, {"Frame Info" ,FRM_INFO_ID,0xffff0000, B_FALSE, 999}, {"EESA Moments" ,EMOM_ID, 0xfff00000, B_FALSE, 224}, {"EESA PADs" ,EHPAD_ID, 0xfff00000, B_FALSE, 248}, {"EESA 3D (Unknown)" ,E3D_UNK_ID, 0xfff0f000, B_FALSE, 0 }, {"EESA 3D (Cut)" ,E3D_CUT_ID, 0xfff0f000, B_FALSE, 480}, {"EESA 3D (88 A)" ,E3D_88_ID, 0xfff0f000, B_FALSE, 495,1320 /*,0x07 */}, {"EESA 3D Burst" ,E3D_BRST_ID,0x77f0f000, B_TRUE, 0}, {"PESA 3D" ,P3D_ID, 0xfff00000, B_FALSE, 390,1890 /* ,0x07,1 */}, {"PESA 3D Burst" ,P3D_BRST_ID,0x77f00000, B_TRUE, 0}, {"PESA Moments" ,PMOM_ID, 0xfff00000, B_FALSE, 320}, {"PESA Snapshot" ,PLSNAP_ID, 0xffff0000, B_FALSE, 350}, {"Correlator data" ,FPC_D_ID, 0x77f00000, B_TRUE, 0}, {"Correlator slice" ,FPC_P_ID, 0x77f00000, B_TRUE, 0}, {"Correlator dump" ,FPC_DUM_ID, 0x77f00000, B_TRUE, 999}, {"MAIN CC Mem Dump" ,M_MEM_ID ,0xffff0000, B_FALSE, 158}, {"EESA CC Mem Dump" ,E_MEM_ID ,0xffff0000, B_FALSE, 182}, {"PESA CC Mem Dump" ,P_MEM_ID ,0xffff0000, B_FALSE, 158}, {"MAIN HKP A/D" ,M_HKP_ID ,0xffff0000, B_FALSE, 64}, {"EESA HKP A/D" ,E_A2D_ID ,0xffff0000, B_FALSE, 64}, {"PESA HKP A/D" ,P_A2D_ID ,0xffff0000, B_FALSE, 64}, {"RAM Mem Dump" ,R_MEM_ID ,0xffff0000, B_FALSE, 999}, {"MAIN Config" ,M_CFG_ID ,0xffffffff, B_FALSE, 110}, {"EESA Config" ,E_CFG_ID ,0xffffffff, B_FALSE, 144}, {"PESA Config" ,P_CFG_ID ,0xffffffff, B_FALSE, 218}, {"MAIN Extended Cfg" ,M_XCFG_ID ,0xffffffff, B_FALSE, 432}, {"EESA Extended Cfg" ,E_XCFG_ID ,0xffff0000, B_FALSE, 422}, {"PESA Extended Cfg" ,P_XCFG_ID ,0xffffffff, B_FALSE, 999}, {"SST Flat Rate" ,S_RATE_ID ,0xffff0000, B_FALSE, 999}, {"SST R+S 1" ,S_RATE1_ID,0xffff0000, B_FALSE, 353}, {"SST R+S Burst" ,0x34100000,0x77f00000, B_TRUE, 0}, {"SST R+S 3" ,0x40100000,0xfff00000, B_FALSE, 353}, {"SST T-dist Burst" ,0x34200000,0x77f00000, B_TRUE, 0}, {"SST T-dist" ,0x40200000,0xfff00000, B_FALSE, 256}, {"SST Half spin Burst",0x34300000,0x77f00000, B_TRUE, 0}, {"SST 3D-O data" ,0x40400000,0xfff00000, B_FALSE, 0}, {"SST 3D-F data" ,0x40500000,0xfff00000, B_FALSE, 0}, {"SST PAD data" ,0x40600000,0xfff00000, B_FALSE, 0}, {"PESA Spectra" ,PSPECT_ID ,0xfffd0000, B_FALSE, 71}, {"PESA PHA" ,0x28600000,0xffff0000, B_FALSE, 999}, {"PESA Fast Rate" ,0x28800000,0xfff00000, B_FALSE, 55}, {"PESA Burst Dump" ,0x36200000,0xfff00000, B_TRUE, 55}, {"PESA Burst Snapshot",0x36800000,0x77f00000, B_TRUE, 0}, {"EESA Spectra" ,ESPECT_ID, 0xfffd0000, B_FALSE, 71}, {"EESA Fast Rate" ,0x18800000,0xfff00000, B_FALSE, 55}, {"EESA PHA" ,E_PHA_ID, 0xfff00000, B_FALSE, 0}, {"EESA High Burst" ,EH_BRST_ID,0x77f0f000, B_TRUE, 0}, {"EESA 3D Merge" ,E3D_ELM_ID,0x77f0f000, B_TRUE, 0}, #if 0 {"Pkt 1201" ,0x12010000,0xffff0000, B_FALSE, 999}, {"Pkt 1810" ,0x18100000,0xffff0000, B_FALSE, 999}, {"Pkt 2201" ,0x22010000,0xffff0000, B_FALSE, 999}, {"Pkt 0201" ,0x02010000,0xffff0000, B_FALSE, 999}, {"Pkt 2810" ,0x28100000,0xffff0000, B_FALSE, 999} #endif }; #define NUM_PKT_TYPES (sizeof(packet_types)/sizeof(pklist)) static char *hugemem; /* starting location of static memory */ static char *buffptr; /* current pointer to unused memory */ static int4 hugememsize; /* size of memory */ static int4 memory_left; /* bytes of memory left */ static int4 series_num;/* counter that is incremented with each new data load */ int initialize_raw_memory(int size,void *buff) { int i; pklist *pkl; series_num++; /* increment series number so that reloads are detected */ for(i=0;istore = 1; pkl->print = 1; pkl->numtotal = 0; pkl->numlist = 0; pkl->numarray = 0; pkl->first = pkl->last = pkl->current = NULL; if(hugemem && pkl->array) free((char *)pkl->array); pkl->array = NULL; } #if 0 if(hugemem){ free(hugemem); hugemem = (char *) malloc(size); buff = (void *) hugemem; } else{ hugemem = (char*)buff; } #else hugemem = (char *)buff; #endif if(hugemem == NULL){ fprintf(stderr,"Can not allocate %lu bytes\n",(ulong)size); hugememsize = memory_left = 0; size = 0; } buffptr = hugemem; hugememsize = memory_left = size; return(buff ? series_num : 0); } long find_mem_left() { int4 d; d = memory_left; return(d); } #if 0 int free_unused_memory(int *size,void *buff) { int4 d; if (memory_left > 0) { d = hugememsize - memory_left; realloc((void *)hugemem,d); memory_left = 0; hugememsize = d; buff = (void *)hugemem; *size = hugememsize; } return(0); } #else #endif int memory_filled() { uint2 size; size = ((size/sizeof(Align))+1)*sizeof(Align); if ((memory_left <= size+3000+PDSIZE) && (!batch_print)) return(1); else return(0); } #if 0 int set_print_packet_flag(int n) { if(n>=0 && ntime = time; pk->spin = packet_spin(ds); pk->idtype = packet_idtype(ds); pk->instseq= packet_instseq(ds); pk->dsize = packet_dsize(ds); pk->quality = (errors >> 16); pk->errors = errors; size = pk->dsize + PDSIZE; /* bytes of mem used */ memcpy( pk->data, ds+8 , pk->dsize ); /* store data after the header */ size = ((size/sizeof(Align))+1)*sizeof(Align); /* alignment round up */ /* Provide permanent storage only if space for this packet and */ /* at least 1 other packet is available */ if(list->store && (memory_left > size+3000+PDSIZE) ){ add_packet_to_list(pk,list); buffptr += size; /* provide permanent storage */ memory_left -= size; } else{ pk->next = pk->prev = 0; /* safety measure */ } return(list->seqmask ? 0 : pk); } int add_packet_routine(uint4 id,Packet_Routine routine) { int index; index = packet_type_index(id); packet_types[index].pckt_print = routine; return(index); /* Not finished */ } void add_packet_to_list(packet *pk,pklist *list) { if(list->first ==0) /* If first packet: */ list->first = pk; /* mark as such. */ pk->prev = list->last; if(pk->prev) /* Set prev packet (if any) */ pk->prev->next = pk; /* pointing to this packet. */ pk->next = 0; /* Adding to end of list. */ list->last = pk; /* Set last member to this packet. */ list->numlist++; /* Increment list counter. */ } void mark_last_packet_as_suspect(int id) { pklist *list; packet *pk; list = &packet_types[id]; if((list->first ==0) || (id == 0)) return; pk = list->last; pk->quality |= QUALITY_INVALID_NEXTP; } /* returns the packet type index for valid packets /* returns 0 if the packet id is unknown */ /* guaranteed to return a valid index */ int packet_type_index(uint4 id) { packet *pk; pklist *pkl; int i; for(i=NUM_PKT_TYPES-1; i>=0; i--){ pkl = &packet_types[i]; if(pkl->id == (id & pkl->idmask) ) break; } return(i); } int process_packet(double time,uint errors,uchar *ds) { /* returns 0 when given unknown packet type */ packet *pk; pklist *pkl; int i,dsize; uint4 id; id = packet_id(ds); i = packet_type_index(id); pkl = &packet_types[i]; pkl->numtotal++; pk = store_packet(time,errors,ds,pkl); if(batch_print && pk){ /* Batch printing routines */ if(pckt_log_fp) packet_log(pckt_log_fp,pk); if(pkl->print && pkl->pckt_print) (*(pkl->pckt_print))(pk); /* print packet if needed */ } return(i); } packet *search_for_packet(double time,pklist *pkl) { /* looks backward through list to find packet with same or earlier time */ packet *pk; /* assumes list is in chronological order */ if(! pkl->current) pkl->current = pkl->last; pk = pkl->current; /* starts search with last used packet */ while(pk && pk->next && pk->time < time) pk = pk->next; while(pk && pk->prev && pk->time > time) pk = pk->prev; pkl->current = pk; return(pk); /* returns null if no packet is found */ } #if 0 int number_of_packets(uint4 id,double t1,double t2) { pklist *pkl; packet *pk; double lt; int n = 0; pkl = packet_type_ptr(id); if(pkl){ pk = get_next_packet(t1,pkl); if(pk) t1 = pk->time; while(pk && pk->time <= t2){ lt = pk->time; pk = get_next_packet(pk->time,pkl); n++; } if(n && lt < t2) /* extrapolate */ n = n*((t2-t1)/(lt - t1)); } return(n); } #else /* Returns the number of packets (data samples) with the given id. */ int number_of_packets(uint4 id,double t1,double t2) { pklist *pkl; pkl = packet_type_ptr(id); if(pkl) return(pkl->numlist); else return(0); } #endif packet *get_packet_3(double time,PACKET_ID id,int direction) { packet *pk; packet_selector pks; SET_PKS_BY_TIME(pks,time,id) ; pk = get_packet(&pks); return(pk); } /* get_packet(packet_selector *pks) * double pks->time: time of packet to get. Ignored if getting by index. * long pks->index: index of packet to get. Ignored if getting by time. * PACKET_ID pks->id: PACKET_ID of interest. * int pks->direction: (-1,0,+1) : (previous,nearest,next). * packet *pks->lastpk: gives a hint of where to start search. * int pks->series: series identifcation tag (insures that lastpk is valid. * SELECTION_METHOD pks->select_by: select by index or by time. * usage: * if pks->time is greater than 0: * gets the packet with the given id that has: * time less than pks->time (direction <0) * time nearest to pks->time (direction =0) * time greater than pks->time (direction >0) * if t is 0: * gets (previous/nearest/next) packet based on value of lastpk * Note: pks->lastpk and pks->series should be stored as static variables generally */ /* returns the packet pointer (null if there is no such packet) */ packet * get_packet(packet_selector *pks) { packet *pk; /* assumes list is in chronological order */ pklist *pkl; int l; pkl = packet_type_ptr(pks->id); if(pkl==0 || pkl->numarray==0) return(0); #if 0 /* temporary test */ if(pks->direction <0) return(search_for_packet(pks->time,pkl)); #endif if(pks->series != series_num || pks->lastpk==0){ /* reset */ pks->series = series_num; /* packet pointer was invalid */ pks->lastpk = (pks->direction>0) ? pkl->last : pkl->first; } /* we will select the return packet by index in the array of packets, * or by time */ switch ( pks->select_by ) { case BY_INDEX: /* index in range? */ if ( pks->index < 0 || pks->index >= pkl->numarray ) return (packet *) NULL ; pk = pkl->array[pks->index] ; pks->time = pk->time; break; case BY_TIME: if(pks->time ==(double)0.){ if(pks->direction>0) pks->index ++ ; else if(pks->direction<0) pks->index -- ; /* index in range? */ if ( pks->index < 0 || pks->index >= pkl->numarray) return (packet *) NULL ; pk = pkl->array[pks->index]; break; } else{ /* use time value; use pks->index as a starting point */ int idx; if(pks->direction>0) { /* make index in range */ if ( pks->index < 0 || pks->index >= pkl->numarray ) pks->index = 0; idx = pks->index ; for ( ; idx > 0 && (((pkl->array[idx])->time > pks->time) || ((pkl->array[idx])->quality & ~pkquality)) ; idx -- ) ; for ( ; idx < (pkl->numarray - 1) && (((pkl->array[idx])->time <= pks->time) || ((pkl->array[idx])->quality & ~pkquality)) ; idx ++ ) ; } else{ /* make index in range */ if ( pks->index < 0 || pks->index >= pkl->numarray ) pks->index = pkl->numarray-1; idx = pks->index ; for ( ; idx < (pkl->numarray - 1) && (((pkl->array[idx])->time < pks->time) || ((pkl->array[idx])->quality & ~pkquality)) ; idx ++ ) ; for ( ; idx > 0 && (((pkl->array[idx])->time > pks->time) || ((pkl->array[idx])->quality & ~pkquality)) ; idx -- ) ; } pk = pkl->array[idx] ; pks->index = idx ; pks->time = pk->time ; } break; default: return (packet *) NULL ; break; } /* #define PK_DEBUG */ #if defined (PK_DEBUG) printf("%s: time: %lf, direction: %d, &packet %lx\n\r", __FILE__, pks->time, pks->direction, pk); printf("packet {" "\n\r\ttime: %lf" "\n\r\tspin: %hd" "\n\r\tdsize: %hd" "\n\r\tidtype %hd" "\n\r\tinstseq: %hd" "\n\r\tquality: %hd" "\n\r\tterrors: %hd" "\n\r\tprev: %lx" "\n\r\tnext: %lx" "\n\r\tdata: \r\n", pk->time, pk->spin, pk->dsize, pk->idtype, pk->instseq, pk->quality,pk->errors, pk->prev, pk->next); for ( l=0; ldata[l]); printf("\n\r\t}\n\r"); #endif /* defined (PK_DEBUG) */ pks->lastpk = pk; return(pk); /* returns null if no packet is found */ } int pk_time_compare(const void * v1, const void *v2) { packet *p1 = *(packet **)v1; packet *p2 = *(packet **)v2; if (p1->time == p2->time) return (0); return (p1->time > p2->time) ? 1 : -1; } boolean_t is_sortable(pklist pkl) { return (pkl.array && pkl.sortable) ? B_TRUE : B_FALSE; } void make_all_list_arrays() { int i; for(i=0;iarray){ /* if memory is already allocated, then just reuse it for efficiency */ fprintf(debug,"%s array already malloced!\n",pkl->name); /* there will be a little wasted space in the following, but * it won't be much, so who cares */ num_mallocs = elmpkl->numarray % NUM_MALLOC_AT_ONCE ; elements_left = num_mallocs * NUM_MALLOC_AT_ONCE ; } else { /* We don't know how many array elements we need at first, so * we will use a scheme that mallocs NUM_MALLOC_AT_ONCE elements of * the packet array at one time. When we run out of array slots, * realloc some more. */ elmpkl->array = (packet **)malloc(NUM_MALLOC_AT_ONCE * sizeof(packet *)); } pkl = &packet_types[packet_type_index(E3D_88_ID)]; if(pkl->array){ pk = pkl->first ; for(i=0, j=0;inumlist && pk;i++,pk=pk->next) { if (first || spin != pk->spin) { elmpkl->array[j] = pk; j++; elmpkl->numarray = j; spin = pk->spin ; first = B_FALSE ; /* check to see if we need more array elements, and get 'em if necessary */ elements_left -- ; if ( ! elements_left ) { num_mallocs ++ ; elmpkl->array = (packet **)realloc(elmpkl->array, NUM_MALLOC_AT_ONCE * num_mallocs * sizeof(packet *)); elements_left = NUM_MALLOC_AT_ONCE ; } } } } first = B_TRUE; pkl = &packet_types[packet_type_index(E3D_BRST_ID)]; if(pkl->array){ pk = pkl->first ; for(i=0;inumlist && pk;i++,pk=pk->next) { if (first || spin != pk->spin) { elmpkl->array[j] = pk; j++; elmpkl->numarray = j; spin = pk->spin ; first = B_FALSE ; /* check to see if we need more array elements, and get 'em if necessary */ elements_left -- ; if ( ! elements_left ) { num_mallocs ++ ; elmpkl->array = (packet **)realloc(elmpkl->array, NUM_MALLOC_AT_ONCE * num_mallocs * sizeof(packet *)); elements_left = NUM_MALLOC_AT_ONCE ; } } } } } /* creates array of pointers to packets */ void list_to_array(pklist *pkl) { packet *pk; uint i, j; uint2 spin = 0xffff; int elements_left = NUM_MALLOC_AT_ONCE; int num_mallocs = 1 ; boolean_t first = B_TRUE ; if (! pkl->numlist) return; if(pkl->array){ /* if memory is already allocated, then just reuse it for efficiency */ fprintf(debug,"%s array already malloced!\n",pkl->name); /* there will be a little wasted space in the following, but * it won't be much, so who cares */ num_mallocs = pkl->numarray % NUM_MALLOC_AT_ONCE ; elements_left = num_mallocs * NUM_MALLOC_AT_ONCE ; } else { /* We don't know how many array elements we need at first, so * we will use a scheme that mallocs NUM_MALLOC_AT_ONCE elements of * the packet array at one time. When we run out of array slots, * realloc some more. */ pkl->array = (packet **)malloc(NUM_MALLOC_AT_ONCE * sizeof(packet *)); } if(pkl->array){ pk = pkl->first ; for(i=0, j=0;inumlist && pk;i++,pk=pk->next) { if (first || spin != pk->spin) { pkl->array[j] = pk; j++; pkl->numarray = j; spin = pk->spin ; first = B_FALSE ; /* check to see if we need more array elements, and get 'em if necessary */ elements_left -- ; if ( ! elements_left ) { num_mallocs ++ ; pkl->array = (packet **)realloc(pkl->array, NUM_MALLOC_AT_ONCE * num_mallocs * sizeof(packet *)); elements_left = NUM_MALLOC_AT_ONCE ; } } } if(inumlist || pk) /* this should never occur */ fprintf(stderr,"Error in producing %s list array\n" ,pkl->name); } else{ pkl->numarray = 0; fprintf(debug,"Unable to malloc memory for %s\n",pkl->name); } } /* The following routines can be replaced with macros to increase speed */ uint4 packet_id(uchar *p) { return( ((uint4)p[0]<<24) | ((uint4)p[6]<<16) | (p[7]<<8) | p[1] ); } uint2 packet_idtype(uchar *p){ return( (p[0]<<8) + p[6] ); } uint2 packet_size(uchar *p){ return( (p[3] << 8) + p[2] ); } /* packet size */ uint2 packet_dsize(uchar *p){ return( (p[3] << 8) + p[2] - 8 ); } /* packet data size */ uint2 packet_spin(uchar *p){ return( (p[5] << 8) + p[4] ); } uint2 packet_instseq(uchar *p){ return( (p[7]<<8) + p[1] ); } /*uchar packet_seq(uchar *p){ return( p[1] ); } */ /*uchar packet_inst(uchar *p){ return( p[7] ); } */ /*********** THE REMAINDER OF THIS FILE DEALS WITH PRINTING PACKETS *****/ /*------------------------------------------------------------------------------ | PACKET_TYPE_PTR() | |------------------------------------------------------------------------------| | | | PURPOSE | | ------- | | This function returns a pointer to the requested packet type structure. The | | structure pointed to contains all information and data associated with that | | packet. | | | | ARGUMENTS | | --------- | | id_type input: requested packet id/type hex value | | | | RETURN | | ------ | | pkt_ptr pointer to packet type structure | | | | AUTHOR | | ------ | | Todd H. Kermit, Space Sciences Laboratory, U.C. Berkeley | | | ------------------------------------------------------------------------------*/ pklist* packet_type_ptr(uint4 id) { return( &packet_types[packet_type_index( id )] ); } #if 0 pklist* packet_type_ptr(uint4 id) { pklist* ptr = packet_types; /* pkt struct pointer */ int4 i; /* generic counter */ int4 n_types = NUM_PKT_TYPES; /* number of pkt types*/ /* Search for requested packet type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ for (i = 0; i < n_types; i++) { if (ptr->id == id) break; ptr++; } /* Return NULL pointer if packet type not found ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if (i == n_types) return((pklist*) NULL); /* Done ~~~~ */ return(ptr); } #endif #if 0 #include /*------------------------------------------------------------------------------ | GET_PACKET_INDEX() | |------------------------------------------------------------------------------| | | | PURPOSE | | ------- | | This function determines the packet index associated with input time for the | | input packet type. | | | | NOTES | | ----- | | A binary search is used to locate the time. | | | | ARGUMENTS | | --------- | | id_type input: requested packet id/type hex value | | time input: requested time | | | | RETURN | | ------ | | indx index number of packet containing requested time | | | | AUTHOR | | ------ | | Todd H. Kermit, Space Sciences Laboratory, U.C. Berkeley | | | ------------------------------------------------------------------------------*/ int4 get_packet_index(uint4 id, double time) { packet** pkt; /* pointer to packets */ pklist* ptr; /* pkt struct pointer */ double val; /* current value */ int4 indx; /* index */ int4 indx1; /* lower index */ int4 indx2; /* upper index */ /* Search for requested packet type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if ((ptr = packet_type_ptr(id)) == NULL) return(ERROR); /* Initialize indices -- handle single point, if necessary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ indx1 = 0; indx2 = ptr->numarray - 1; if (indx1 == indx2) return(indx1); /* Assign pointer to packets ~~~~~~~~~~~~~~~~~~~~~~~~~ */ pkt = ptr->array; /* Locate lower and upper indices using binary search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ while ((indx2 - indx1) > 1) { indx = (indx1 + indx2) / 2; val = pkt[indx]->time; if (val == time) { indx1 = indx; break; } else if (val < time) indx1 = indx; else if (val > time) indx2 = indx; } /* Determine which index number is closest ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ if (fabs(pkt[indx1]->time - time) < fabs(pkt[indx2]->time - time)) indx = indx1; else indx = indx2; /* Done ~~~~ */ return(indx); } #endif void print_packet_summary(FILE *fp) { int i; pklist *pkl; fprintf(fp," # Description: ID Obs Stored\n"); for(i=0;iname, pkl->id>>16,pkl->numtotal,pkl->numlist); if(pkl->first) fprintf(fp," %s ",time_to_YMDHMS(pkl->first->time)); if(pkl->last) fprintf(fp," %s ",time_to_YMDHMS(pkl->last->time)); fprintf(fp,"\n"); /* print_list( pkl ); */ } }