diff --git a/src/sbbs3/sexyz.c b/src/sbbs3/sexyz.c index f18666f5e87dc3b4df87055251299c19f238c040..d8f8594ffed55c5c48c2dcf9e29e2f3f6d45f2cb 100644 --- a/src/sbbs3/sexyz.c +++ b/src/sbbs3/sexyz.c @@ -80,7 +80,6 @@ long mode=0; /* Program mode */ long zmode=0L; /* Zmodem mode */ uchar block[1024]; /* Block buffer */ ulong block_num; /* Block number */ -time_t startall; char* dszlog; xmodem_t xm; @@ -155,7 +154,7 @@ static BOOL winsock_startup(void) #endif -int lputs(void* unused, int level, const char* str) +static int lputs(void* unused, int level, const char* str) { FILE* fp=statfp; @@ -172,7 +171,7 @@ int lputs(void* unused, int level, const char* str) return fprintf(fp,"%s\n",str); } -int lprintf(int level, const char *fmt, ...) +static int lprintf(int level, const char *fmt, ...) { char sbuf[1024]; va_list argptr; @@ -184,12 +183,13 @@ int lprintf(int level, const char *fmt, ...) return(lputs(NULL,level,sbuf)); } -char *chr(uchar ch) +static char *chr(uchar ch) { static char str[25]; if(mode&ZMODEM) { switch(ch) { + case ZACK: return("ZACK"); case ZPAD: return("ZPAD"); case ZDLE: return("ZDLE"); case ZDLEE: return("ZDLEE"); @@ -420,7 +420,7 @@ int send_byte(void* unused, uchar ch, unsigned timeout) } #endif -void output_thread(void* arg) +static void output_thread(void* arg) { char stats[128]; BYTE buf[IO_THREAD_BUF_SIZE]; @@ -653,7 +653,7 @@ void zmodem_progress(void* unused, ulong offset, ulong fsize, time_t start) } } -int send_files(char** fname, uint fnames) +static int send_files(char** fname, uint fnames) { char path[MAX_PATH+1]; int i; @@ -663,17 +663,15 @@ int send_files(char** fname, uint fnames) glob_t g; int gi; BOOL success=TRUE; - long l; long fsize; - long block_len; - uint total_files=0,sent_files=0; - ulong total_bytes=0,sent_bytes=0; - ulong total_blocks; - size_t n; + ulong sent_bytes; + ulong total_bytes=0; time_t t,startfile; - time_t now; + time_t startall; FILE* fp; + startall=time(NULL); + /****************************************************/ /* Search through all to find total files and bytes */ /****************************************************/ @@ -685,18 +683,18 @@ int send_files(char** fname, uint fnames) for(i=0;i<(int)g.gl_pathc;i++) { if(isdir(g.gl_pathv[i])) continue; - total_files++; - total_bytes+=flength(g.gl_pathv[i]); + xm.total_files++; + xm.total_bytes+=flength(g.gl_pathv[i]); } globfree(&g); } - if(fnames>1) + if(xm.total_files>1) lprintf(LOG_INFO,"Sending %u files (%lu KB total)" - ,total_files,total_bytes/1024); + ,xm.total_files,xm.total_bytes/1024); - zm.n_files_remaining = total_files; - zm.n_bytes_remaining = total_bytes; + zm.n_files_remaining = xm.total_files; + zm.n_bytes_remaining = xm.total_bytes; /***********************************************/ /* Send every file matching names or filespecs */ @@ -722,131 +720,53 @@ int send_files(char** fname, uint fnames) success=FALSE; startfile=time(NULL); - do { /* try */ - - if(!(mode&ZMODEM)) { /* X/Ymodem */ - if(!xmodem_get_mode(&xm)) { - xmodem_cancel(&xm); - break; - } - } + lprintf(LOG_INFO,"Sending %s (%lu KB) via %s" + ,path,fsize/1024 + ,mode&XMODEM ? "Xmodem" : mode&YMODEM ? "Ymodem" : "Zmodem"); - lprintf(LOG_INFO,"Sending %s (%lu KB) via %s" - ,path,fsize/1024 - ,mode&XMODEM ? "Xmodem" : mode&YMODEM ? mode&GMODE ? "Ymodem-G" - : "Ymodem" : "Zmodem"); - - if(mode&ZMODEM) { - - success=zmodem_send_file(&zm,getfname(path),fp,fnum==0); - - } else { /* X/Ymodem */ - - if(!(mode&XMODEM)) { - t=fdate(path); - memset(block,0,sizeof(block)); - SAFECOPY(block,getfname(path)); - i=sprintf(block+strlen(block)+1,"%lu %lo 0 0 %d %ld" - ,fsize,t,total_files-sent_files,total_bytes-sent_bytes); - - lprintf(LOG_INFO,"Sending Ymodem header block: '%s'",block+strlen(block)+1); - - block_len=strlen(block)+1+i; - for(errors=0;errors<xm.max_errors;errors++) { - xmodem_put_block(&xm, block, block_len <=128 ? 128:1024, 0 /* block_num */); - if(xmodem_get_ack(&xm,1,0)) - break; - } - if(errors==xm.max_errors) { - lprintf(LOG_ERR,"Failed to send header block"); - xmodem_cancel(&xm); - break; - } - if(!xmodem_get_mode(&xm)) { - xmodem_cancel(&xm); - break; - } - } - startfile=time(NULL); /* reset time, don't count header block */ - block_num=1; - errors=0; - while((block_num-1)*xm.block_size<(ulong)fsize && errors<xm.max_errors) { - fseek(fp,(block_num-1)*(long)xm.block_size,SEEK_SET); - memset(block,CPMEOF,xm.block_size); - if((n=fread(block,1,xm.block_size,fp))!=xm.block_size - && block_num*xm.block_size<(ulong)fsize) { - lprintf(LOG_ERR,"READ ERROR %d instead of %d at offset %lu" - ,n,xm.block_size,(block_num-1)*(long)xm.block_size); - errors++; - continue; - } - xmodem_progress(xm.cbdata,block_num,ftell(fp),fsize,startfile); - xmodem_put_block(&xm, block, xm.block_size, block_num); - now=time(NULL); - total_blocks=num_blocks(fsize,xm.block_size); - if(!xmodem_get_ack(&xm,5,block_num)) { - errors++; - lprintf(LOG_WARNING,"Error #%d at offset %ld" - ,errors,ftell(fp)-xm.block_size); - } else - block_num++; - } - if((long)(block_num-1)*(long)xm.block_size>=fsize) { + if(mode&ZMODEM) + success=zmodem_send_file(&zm, path, fp, /* ZRQINIT? */fnum==0, &startfile, &sent_bytes); + else /* X/Ymodem */ + success=xmodem_send_file(&xm, path, fp, &startfile, &sent_bytes); -#if !SINGLE_THREADED - lprintf(LOG_DEBUG,"Waiting for output buffer to empty... "); - if(WaitForEvent(outbuf_empty,5000)!=WAIT_OBJECT_0) - lprintf(LOG_WARNING,"FAILURE"); -#endif - success=xmodem_put_eot(&xm); /* end-of-text, wait for ACK */ - } - } - } while(0); - /* finally */ fclose(fp); if((t=time(NULL)-startfile)<=0) t=1; - cps=fsize/t; + cps=sent_bytes/t; if(success) { - sent_files++; - sent_bytes+=fsize; + xm.sent_files++; + xm.sent_bytes+=fsize; lprintf(LOG_INFO,"Successful - Time: %lu:%02lu CPS: %lu" ,t/60,t%60,cps); } else lprintf(LOG_WARNING,"File Transfer Failure"); - if(total_files>1) + if(xm.total_files-xm->sent_files) lprintf(LOG_INFO,"Remaining - Time: %lu:%02lu Files: %u KBytes: %lu" - ,((total_bytes-sent_bytes)/cps)/60 - ,((total_bytes-sent_bytes)/cps)%60 - ,total_files-sent_files - ,(total_bytes-sent_bytes)/1024 + ,((xm.total_bytes-xm.sent_bytes)/cps)/60 + ,((xm.total_bytes-xm.sent_bytes)/cps)%60 + ,xm.total_files-xm.sent_files + ,(xm.total_bytes-xm.sent_bytes)/1024 ); /* DSZLOG entry */ if(logfp) { lprintf(LOG_DEBUG,"Updating DSZLOG: %s", dszlog); - if(mode&ZMODEM) - l=zm.sent_successfully; - else { - l=(block_num-1)*(long)xm.block_size; - if(l>fsize) - l=fsize; - } fprintf(logfp,"%c %7lu %5u bps %6lu cps %3u errors %5u %4u " "%s -1\n" ,success ? (mode&ZMODEM ? 'z':'S') : (mode&ZMODEM && zm.file_skipped) ? 's' : 'E' - ,l + ,sent_bytes ,115200 /* baud */ - ,l/t + ,cps ,errors ,flows ,xm.block_size ,path); } + total_bytes += sent_bytes; } /* while(gi<(int)g.gl_pathc) */ if(gi<(int)g.gl_pathc)/* error occurred */ @@ -875,16 +795,16 @@ int send_files(char** fname, uint fnames) } } } - if(total_files>1) { + if(xm.total_files>1) { t=time(NULL)-startall; if(!t) t=1; lprintf(LOG_INFO,"Overall - Time %02lu:%02lu KBytes: %lu CPS: %lu" - ,t/60,t%60,sent_bytes/1024,sent_bytes/t); + ,t/60,t%60,total_bytes/1024,total_bytes/t); } return(0); /* success */ } -int receive_files(char** fname, int fnames) +static int receive_files(char** fname, int fnames) { char str[MAX_PATH+1]; int i; @@ -1551,8 +1471,6 @@ int main(int argc, char **argv) } } - startall=time(NULL); - #if !SINGLE_THREADED _beginthread(output_thread,0,NULL); #endif diff --git a/src/sbbs3/sexyz.h b/src/sbbs3/sexyz.h index 11554119b138ba9847f65c6697f7caad76576177..3e33e756375753171ecaa6cbeded5c1576adb996 100644 --- a/src/sbbs3/sexyz.h +++ b/src/sbbs3/sexyz.h @@ -58,5 +58,3 @@ #define NOINP -1 /* input buffer empty (incom only) */ -char* chr(uchar ch); -void bail(int code); \ No newline at end of file diff --git a/src/sbbs3/xmodem.c b/src/sbbs3/xmodem.c index 32050b89e541d24627f3f4f942e558bc47210660..8a91cb088e281cf61c7fe96573d49795d0374a4d 100644 --- a/src/sbbs3/xmodem.c +++ b/src/sbbs3/xmodem.c @@ -35,11 +35,20 @@ * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ +/* Standard headers */ +#include <stdio.h> +#include <sys/stat.h> /* struct stat */ #include <stdarg.h> /* va_list */ -#include "sexyz.h" + +/* smblib */ #include "crc16.h" + +/* xpdev */ #include "genwrap.h" /* YIELD */ -#include "conwrap.h" /* kbhit */ +#include "dirwrap.h" /* getfname */ + +/* sexyz */ +#include "sexyz.h" #define getcom(t) xm->recv_byte(xm->cbdata,t) #define putcom(ch) xm->send_byte(xm->cbdata,ch,xm->send_timeout) @@ -59,6 +68,27 @@ static int lprintf(xmodem_t* xm, int level, const char *fmt, ...) return(xm->lputs(xm->cbdata,level,sbuf)); } +static char *chr(uchar ch) +{ + static char str[25]; + + switch(ch) { + case SOH: return("SOH"); + case STX: return("STX"); + case ETX: return("ETX"); + case EOT: return("EOT"); + case ACK: return("ACK"); + case NAK: return("NAK"); + case CAN: return("CAN"); + } + if(ch>=' ' && ch<='~') + sprintf(str,"'%c' (%02Xh)",ch,ch); + else + sprintf(str,"%u (%02Xh)",ch,ch); + return(str); +} + + void xmodem_put_ack(xmodem_t* xm) { while(getcom(0)!=NOINP) @@ -272,6 +302,7 @@ BOOL xmodem_get_ack(xmodem_t* xm, unsigned tries, unsigned block_num) return(TRUE); if(i==CAN) { if(can) { + xm->cancelled=TRUE; lprintf(xm,LOG_WARNING,"Block %u: !Cancelled remotely", block_num); xmodem_cancel(xm); return(FALSE); @@ -341,7 +372,7 @@ BOOL xmodem_put_eot(xmodem_t* xm) for(errors=0;errors<xm->max_errors;errors++) { - lprintf(xm,LOG_INFO,"Sending end-of-Text indicator (%d)",errors+1); + lprintf(xm,LOG_INFO,"Sending End-of-Text (EOT) indicator (%d)",errors+1); while((ch=getcom(0))!=NOINP) lprintf(xm,LOG_INFO,"Throwing out received: %s",chr((uchar)ch)); @@ -360,6 +391,112 @@ BOOL xmodem_put_eot(xmodem_t* xm) return(FALSE); } +BOOL xmodem_send_file(xmodem_t* xm, const char* fname, FILE* fp, time_t* start, ulong* sent) +{ + BOOL success=FALSE; + ulong sent_bytes=0; + char block[1024]; + size_t block_len; + unsigned block_num; + size_t i; + size_t rd; + time_t startfile; + struct stat st; + unsigned errors; + + if(sent!=NULL) + *sent=0; + + if(start!=NULL) + *start=time(NULL); + + fstat(fileno(fp),&st); + + if(xm->total_files==0) + xm->total_files=1; + + if(xm->total_bytes==0) + xm->total_bytes=st.st_size; + + if(*(xm->mode)&YMODEM) { + + if(!xmodem_get_mode(xm)) { + xmodem_cancel(xm); + return(0); + } + + memset(block,0,sizeof(block)); + SAFECOPY(block,getfname(fname)); + i=sprintf(block+strlen(block)+1,"%lu %lo 0 0 %d %ld" + ,st.st_size + ,st.st_mtime + ,xm->total_files-xm->sent_files + ,xm->total_bytes-xm->sent_bytes); + + lprintf(xm,LOG_INFO,"Sending Ymodem header block: '%s'",block+strlen(block)+1); + + block_len=strlen(block)+1+i; + for(errors=0;errors<xm->max_errors;errors++) { + xmodem_put_block(xm, block, block_len <=128 ? 128:1024, 0 /* block_num */); + if(xmodem_get_ack(xm,1,0)) + break; + } + if(errors==xm->max_errors) { + lprintf(xm,LOG_ERR,"Failed to send header block"); + xmodem_cancel(xm); + return(0); + } + if(!xmodem_get_mode(xm)) { + xmodem_cancel(xm); + return(0); + } + } + startfile=time(NULL); /* reset time, don't count header block */ + if(start!=NULL) + *start=startfile; + + block_num=1; + errors=0; + while(sent_bytes < (ulong)st.st_size && errors<xm->max_errors && !xm->cancelled) { + fseek(fp,sent_bytes,SEEK_SET); + memset(block,CPMEOF,xm->block_size); + if((rd=fread(block,1,xm->block_size,fp))!=xm->block_size + && (long)(block_num*xm->block_size) < st.st_size) { + lprintf(xm,LOG_ERR,"READ ERROR %d instead of %d at offset %lu" + ,rd,xm->block_size,(block_num-1)*(long)xm->block_size); + errors++; + continue; + } + if(xm->progress!=NULL) + xm->progress(xm->cbdata,block_num,ftell(fp),st.st_size,startfile); + xmodem_put_block(xm, block, xm->block_size, block_num); + if(!xmodem_get_ack(xm,5,block_num)) { + errors++; + lprintf(xm,LOG_WARNING,"Error #%d at offset %ld" + ,errors,ftell(fp)-xm->block_size); + } else { + block_num++; + sent_bytes+=rd; + } + } + if(sent_bytes >= (ulong)st.st_size && !xm->cancelled) { + +#if 0 /* !SINGLE_THREADED */ + lprintf(LOG_DEBUG,"Waiting for output buffer to empty... "); + if(WaitForEvent(outbuf_empty,5000)!=WAIT_OBJECT_0) + lprintf(xm,LOG_WARNING,"FAILURE"); +#endif + if(xmodem_put_eot(xm)) /* end-of-text, wait for ACK */ + success=TRUE; + } + + if(sent!=NULL) + *sent=sent_bytes; + + return(success); +} + + const char* xmodem_source(void) { return(__FILE__); diff --git a/src/sbbs3/xmodem.h b/src/sbbs3/xmodem.h index f3984e25f271bebe5a7baf21007b017a96827be9..7ba6aaecbbd65a2d48a49b2262075ac7beba676e 100644 --- a/src/sbbs3/xmodem.h +++ b/src/sbbs3/xmodem.h @@ -46,6 +46,7 @@ typedef struct { void* cbdata; long* mode; + BOOL cancelled; unsigned block_size; unsigned ack_timeout; unsigned byte_timeout; @@ -53,6 +54,10 @@ typedef struct { unsigned recv_timeout; unsigned max_errors; unsigned g_delay; + unsigned total_files; + unsigned total_bytes; + unsigned sent_files; + unsigned sent_bytes; int (*lputs)(void*, int level, const char* str); void (*progress)(void*, unsigned block_num, ulong offset, ulong fsize, time_t t); int (*send_byte)(void*, uchar ch, unsigned timeout); @@ -76,5 +81,6 @@ void xmodem_put_ack(xmodem_t*); void xmodem_put_nak(xmodem_t*, unsigned block_num); int xmodem_get_block(xmodem_t*, uchar* block, unsigned block_num); void xmodem_put_block(xmodem_t*, uchar* block, unsigned block_size, unsigned block_num); +BOOL xmodem_send_file(xmodem_t* xm, const char* fname, FILE* fp, time_t* start, ulong* sent); #endif /* Don't add anything after this line */ \ No newline at end of file diff --git a/src/sbbs3/zmodem.c b/src/sbbs3/zmodem.c index e1fceda481733420ac9b7e90b27afa3e82eff42b..550a2a7e86a0859d39a1ed3739c8cee9586e19b4 100644 --- a/src/sbbs3/zmodem.c +++ b/src/sbbs3/zmodem.c @@ -27,14 +27,16 @@ #include <stdarg.h> /* va_list */ #include <sys/stat.h> /* struct stat */ -#include "sexyz.h" #include "genwrap.h" +#include "dirwrap.h" #include "sockwrap.h" #include "zmodem.h" #include "crc16.h" #include "crc32.h" +#include "sexyz.h" + #define ENDOFFRAME 2 #define FRAMEOK 1 #define TIMEOUT -1 /* rx routine did not receive a character within timeout */ @@ -62,6 +64,33 @@ static int lprintf(zmodem_t* zm, int level, const char *fmt, ...) return(zm->lputs(zm->cbdata,level,sbuf)); } +static char *chr(uchar ch) +{ + static char str[25]; + + switch(ch) { + case ZACK: return("ZACK"); + case ZEOF: return("ZEOF"); + case ZPAD: return("ZPAD"); + case ZDLE: return("ZDLE"); + case ZDLEE: return("ZDLEE"); + case ZBIN: return("ZBIN"); + case ZHEX: return("ZHEX"); + case ZBIN32: return("ZBIN32"); + case ZBINR32: return("ZBINR32"); + case ZVBIN: return("ZVBIN"); + case ZVHEX: return("ZVHEX"); + case ZVBIN32: return("ZVBIN32"); + case ZVBINR32: return("ZVBINR32"); + case ZRESC: return("ZRESC"); + } + if(ch>=' ' && ch<='~') + sprintf(str,"'%c' (%02Xh)",ch,ch); + else + sprintf(str,"%u (%02Xh)",ch,ch); + return(str); +} + /* * read bytes as long as rdchk indicates that * more data is available. @@ -1009,7 +1038,7 @@ zmodem_rx_header_raw(zmodem_t* zm, int to,int errors) c = zmodem_rx(zm, to); if(c == TIMEOUT) { - lprintf(zm,LOG_ERR,"\n!TIMEOUT %s %d",__FILE__,__LINE__); + lprintf(zm,LOG_ERR,"!TIMEOUT %s %d",__FILE__,__LINE__); return c; } @@ -1256,19 +1285,26 @@ zmodem_send_from(zmodem_t* zm, FILE * fp) * (using ZABORT frame) */ -BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) +BOOL zmodem_send_file(zmodem_t* zm, char* fname, FILE* fp, BOOL request_init, time_t* start, ulong* sent) { - long pos; - struct stat s; + BOOL success=FALSE; + ulong sent_bytes=0; + long pos; + struct stat s; unsigned char * p; - uchar zfile_frame[] = { ZFILE, 0, 0, 0, 0 }; - uchar zeof_frame[] = { ZEOF, 0, 0, 0, 0 }; - int type; - int i; + uchar zfile_frame[] = { ZFILE, 0, 0, 0, 0 }; + uchar zeof_frame[] = { ZEOF, 0, 0, 0, 0 }; + int type; + int i; unsigned errors; + if(sent!=NULL) + *sent=0; + + if(start!=NULL) + *start=time(NULL); + zm->file_skipped=FALSE; - zm->sent_successfully=0; if(request_init) { for(errors=0;errors<zm->max_errors;errors++) { @@ -1347,7 +1383,7 @@ BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) p = zm->tx_data_subpacket; - strcpy(p,name); + strcpy(p,getfname(fname)); p += strlen(p) + 1; @@ -1387,7 +1423,6 @@ BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) if(type == ZSKIP) { zm->file_skipped=TRUE; - fclose(fp); lprintf(zm,LOG_WARNING,"!File skipped by receiver"); return(FALSE); } @@ -1396,6 +1431,9 @@ BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) zm->transfer_start = time(NULL); + if(start!=NULL) + *start=zm->transfer_start; + do { /* * fetch pos from the ZRPOS header @@ -1416,17 +1454,18 @@ BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) type = zmodem_send_from(zm,fp); - if(type == ZFERR || type == ZABORT || zm->cancelled) { - fclose(fp); + if(type == ZFERR || type == ZABORT || zm->cancelled) return(FALSE); - } if(type==ZACK) - zm->sent_successfully = ftell(fp); + sent_bytes = ftell(fp); + + if(sent!=NULL) + *sent=sent_bytes; } while (type == ZRPOS || type == ZNAK); - lprintf(zm,LOG_INFO,"\nFinishing transfer on rx of header type: %s", chr((uchar)type)); + lprintf(zm,LOG_INFO,"Finishing transfer on rx of header type: %s", chr((uchar)type)); /* * file sent. send end of file frame @@ -1439,20 +1478,16 @@ BOOL zmodem_send_file(zmodem_t* zm, char* name, FILE* fp, BOOL request_init) zeof_frame[ZP3] = (s.st_size >> 24) & 0xff; zm->raw_trace = FALSE; - for(errors=0;errors<zm->max_errors;errors++) { - lprintf(zm,LOG_INFO,"Sending EOF frame (%u of %u)", errors+1, zm->max_errors); + for(errors=0;errors<zm->max_errors && !zm->cancelled;errors++) { + lprintf(zm,LOG_INFO,"Sending End-of-File (ZEOF) frame (%u of %u)", errors+1, zm->max_errors); zmodem_tx_hex_header(zm,zeof_frame); - if(zmodem_rx_header(zm,zm->recv_timeout==ZRINIT) || zm->cancelled) + if(zmodem_rx_header(zm,zm->recv_timeout==ZRINIT)) { + success=TRUE; break; + } } - /* - * and close the input file - */ - - fclose(fp); - - return(TRUE); + return(success); } #if 0 diff --git a/src/sbbs3/zmodem.h b/src/sbbs3/zmodem.h index 39d45a2cf391d6e22f6fa797a410c7a6f25a0e80..c6089ed925701e16a5768de2b01047b577311e0b 100644 --- a/src/sbbs3/zmodem.h +++ b/src/sbbs3/zmodem.h @@ -247,7 +247,6 @@ typedef struct { /* Status */ BOOL cancelled; BOOL file_skipped; - ulong sent_successfully; /* Configuration */ long* mode; @@ -274,7 +273,7 @@ const char* zmodem_source(void); int zmodem_get_zrinit(zmodem_t*); void zmodem_parse_zrinit(zmodem_t*); int zmodem_send_zfin(zmodem_t*); -int zmodem_send_file(zmodem_t*, char* name, FILE* fp, BOOL request_init); +BOOL zmodem_send_file(zmodem_t*, char* name, FILE* fp, BOOL request_init, time_t* start, ulong* bytes_sent); #endif