#include #include #include #include #include #include #include #define FLG_isEnc (1<<0) #define FLG_isDec (1<<1) #define FLG_isError (1<<2) #ifndef BUF_CAP /* aka "buffer capacity" */ # define BUF_CAP 0x10000 #endif #ifndef INLINE # define INLINE(T) /*TODO static inline*/ T #endif #ifndef PRIVATE # define PRIVATE(T) static T #endif #ifndef REGISTER # define REGISTER /*no-op*/ #endif #if 0 # define LOGD(...) fprintf(stderr, __VA_ARGS__) #else # define LOGD(...) #endif #define STR_QUOT_(S) #S #define STR_QUOT(S) STR_QUOT_(S) typedef struct FalseSharingBarrier { void* _[64/sizeof(void*)]; } FalseSharingBarrier; typedef struct Buf { int len; char buf[BUF_CAP]; } Buf; #define App_mAGIC 0x9C344C73 #define DEFINE_App(D, S) struct App*const D=(void*)S; do{ \ assert(D); assert(D->mAGIC == App_mAGIC); }while(0) typedef struct App { int mAGIC; int flg; pthread_t thrdWr, thrdRd; FalseSharingBarrier _IQExiA5MsnEuw7eW; /* * shared in between processing and destination thread */ char *dstBufReadyToFill; char *dstBufReadyToFlush; pthread_mutex_t mutxDst; pthread_cond_t condDst; FalseSharingBarrier _EY0zt7tDeaLkpOmA; /* * shared in between source and processing thread */ char *srcBufReadyToConsume; char *srcBufReadyToFill; pthread_mutex_t mutxSrc; pthread_cond_t condSrc; FalseSharingBarrier _yZYEomgBwCcRRKws; /* * double-buffering for each (read/write) side. */ FalseSharingBarrier _8mjY6g2I7tL3wQDE; Buf dstBufOne; FalseSharingBarrier _FkFIIpC2JzIaKuJe; Buf dstBufTwo; FalseSharingBarrier _d3xluckkcebLiGzO; Buf srcBufOne; FalseSharingBarrier _y629LZsTzNbe2rka; Buf srcBufTwo; } App; PRIVATE(char) endOfFile; INLINE(void) printHelp( void ){ printf("\n" " quotprint - QuotedPrintable codec\n" " Version: " STR_QUOT(PROJECT_VERSION) "\n" " \n" " -e `encode` to QuotedPrintable\n" " -d `decode` from QuotedPrintable\n" " \n" "\n"); } INLINE(int) parseArgs( int argc, char**argv, App*this ){ int err; assert(argv[0]); assert(this); /* 1st prime config based on filename. Eg to support `quotprintenc` and * `quotprintdec`. */ char *basename = strrchr(argv[0], '/'); basename = (basename) ? basename +1 : argv[0]; if( !strcasecmp(basename, "quotprintenc") ) { this->flg |= FLG_isEnc; } if( !strcasecmp(basename, "quotprintdec") ) { this->flg |= FLG_isDec; } /* then handle regular args */ int iA = 0; nextArg: iA += 1; char *arg = argv[iA]; if( !arg ){ goto verify; }else if( !strcmp(arg, "--help") ){ return -2; }else if( !strcmp(arg, "-e") ){ this->flg |= FLG_isEnc; }else if( !strcmp(arg, "-d") ){ this->flg |= FLG_isDec; }else{ fprintf(stderr, "EINVAL: %s\n", arg); return -1; } goto nextArg; verify: err = FLG_isEnc | FLG_isDec; if( (this->flg & err) == err || (this->flg & err) == 0 ){ fprintf(stderr, "EINVAL: Choose exactly ONE of `encode` XOR `decode`.\n"); return -1; } return 0; } PRIVATE(void*) mainOfOutputThread( void*_ ){ /* TODO shutdown if error on other thread */ int err; #define BUF (this->dstBufReadyToFlush) #define LEN (*(int*)(((uintptr_t)0) \ + ((uintptr_t)(BUF == this->dstBufOne.buf) * (uintptr_t)&this->dstBufOne.len) \ + ((uintptr_t)(BUF == this->dstBufTwo.buf) * (uintptr_t)&this->dstBufTwo.len) \ )) DEFINE_App(this, _); { err = pthread_mutex_lock(&this->mutxDst); assert(!err); }awaitBuffer:{ LOGD("output - await BUF ...\n"); while( !BUF ) pthread_cond_wait(&this->condDst, &this->mutxDst); if( BUF == &endOfFile ) { goto onSrcEof; } LOGD("output - BUF ready\n"); goto writeChunk; }writeChunk:{ assert(LEN > 0); assert(BUF); err = fprintf(stdout, "%.*s", LEN, BUF); if( err < 0 ){ err = -errno; /*TODO maybe handle closed sink gracefully*/ this->flg |= FLG_isError; fprintf(stderr, "ERRNO_%d: fprintf(stdout)\n", -err); } /*clear buf and set unassigned to inidicate we're ready for another one*/ LOGD("output - wrote %d bytes from dstBuf%s to the world\n", err, (BUF==this->dstBufOne.buf)?"One":"Two"); LEN = 0; BUF = NULL; err = pthread_cond_broadcast(&this->condDst); assert(!err); goto awaitBuffer; }onSrcEof:{ LOGD("output - EOF received\n"); err = 0; goto resolveWithErr; }resolveWithErr:{ assert(err == 0); LOGD("RETURN from %s()\n", __func__); err = pthread_mutex_unlock(&this->mutxDst); assert(!err); return NULL; } #undef BUF #undef LEN } PRIVATE(void*) mainOfInputThread( void*_ ){ /* TODO shutdown if error on other thread */ int err; DEFINE_App(this, _); #define BUF (this->srcBufReadyToFill) /*TODO branchless*/ #define LEN (*((BUF == this->srcBufOne.buf) ? (&this->srcBufOne.len) : (&this->srcBufTwo.len))) { err = pthread_mutex_lock(&this->mutxSrc); assert(!err); goto awaitBuffer; }awaitBuffer:{ LOGD("innput - await BUF ...\n"); while( !BUF ) pthread_cond_wait(&this->condSrc, &this->mutxSrc); LOGD("innput - BUF ready\n"); goto readNextChunk; }readNextChunk:{ #define SRC stdin assert(BUF); assert(LEN == 0); err = fread(BUF, 1, BUF_CAP, SRC); if( err == 0 && ferror(SRC) ){ err = -errno; /*TODO maybe handle closed sink gracefully*/ this->flg |= FLG_isError; fprintf(stderr, "ERRNO_%d: " STR_QUOT(PROJECT_NAME) ": fread()\n", -err); goto resolveVoid; } if( err == 0 && feof(SRC) ) { goto onSrcEof; } assert(err > 0); /* mark buffer filled and unassing to indicate we're ready for another * one to fill */ LOGD("Filled %d bytes into srcBuf%s\n", err, (BUF==this->srcBufOne.buf)?"One":"Two"); LEN = err; /*make sure slot is free for assignment*/ while( this->srcBufReadyToConsume ){ err = pthread_cond_wait(&this->condSrc, &this->mutxSrc); assert(!err); } this->srcBufReadyToConsume = BUF; BUF = NULL; err = pthread_cond_broadcast(&this->condSrc); assert(!err); goto awaitBuffer; #undef SRC }onSrcEof:{ LOGD("innput - EOF\n"); this->srcBufReadyToConsume = &endOfFile; err = pthread_cond_broadcast(&this->condSrc); assert(!err); goto resolveVoid; }resolveVoid:{ LOGD("RETURN from %s()\n", __func__); err = pthread_mutex_unlock(&this->mutxSrc); assert(!err); return NULL; } #undef BUF #undef LEN } INLINE(void*) mainOfEncode( void*_ ){ DEFINE_App(this, _); int err, iSrc = 0, iDst = 0; { #define BUF_DST (this->dstBufReadyToFill) #define DST_CAP BUF_CAP #define LEN_DST (*(int*)(((uintptr_t)0) \ + ((uintptr_t)(BUF_DST == this->dstBufOne.buf) * (uintptr_t)&this->dstBufOne.len) \ + ((uintptr_t)(BUF_DST != this->dstBufOne.buf) * (uintptr_t)&this->dstBufTwo.len) \ )) #define BUF_SRC (this->srcBufReadyToConsume) #define SRC_CAP BUF_CAP #define LEN_SRC (*(int*)(((uintptr_t)0) \ + ((uintptr_t)(BUF_SRC == this->srcBufOne.buf) * (uintptr_t)&this->srcBufOne.len) \ + ((uintptr_t)(BUF_SRC == this->srcBufTwo.buf) * (uintptr_t)&this->srcBufTwo.len) \ )) }awaitBuffers:{ LOGD("encoder - await DST buf\n"); err = pthread_mutex_lock(&this->mutxDst); assert(!err); while( !BUF_DST ) pthread_cond_wait(&this->condDst, &this->mutxDst); err = pthread_mutex_unlock(&this->mutxDst); assert(!err); }{ LOGD("encoder - await SRC buf\n"); err = pthread_mutex_lock(&this->mutxSrc); assert(!err); while( !BUF_SRC ) pthread_cond_wait(&this->condSrc, &this->mutxSrc); err = pthread_mutex_unlock(&this->mutxSrc); assert(!err); }{ LOGD("encoder - both buffers ready\n"); if( BUF_SRC == &endOfFile ){ /* input has ended. we MUST ensure that we really flush what * we maybe still have in dst. So assert dst is full and handle it * there. */ goto onDstBufFull; } for(;;){ if( iDst >= DST_CAP ) { goto onDstBufFull; } if( iSrc >= LEN_SRC ) { goto onSrcBufDrained; } uint_fast8_t c = BUF_SRC[iSrc++]; switch( c ){ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: BUF_DST[iDst++] = c; } } assert(!"unreachable"); }onDstBufFull:{ /* hand over dst buf as soon consumer is ready to take it */ err = pthread_mutex_lock(&this->mutxDst); assert(!err); while( this->dstBufReadyToFlush ) { err = pthread_cond_wait(&this->condDst, &this->mutxDst); assert(!err); } LOGD("encoder - dstBufReadyToFlush := dstBuf%s\n", (BUF_DST==this->dstBufOne.buf)?"One":"Two"); LOGD("encoder - dstBuf%s.len := %d\n", (BUF_DST==this->dstBufOne.buf)?"One":"Two", iDst); LOGD("encoder - dstBuf%s := NULL\n", (BUF_DST==this->dstBufOne.buf)?"One":"Two"); this->dstBufReadyToFlush = BUF_DST; LEN_DST = iDst; BUF_DST = NULL; err = pthread_cond_broadcast(&this->condDst); assert(!err); if( BUF_SRC == &endOfFile ){ /* src eof, so mark our eof too (as soon prev buffer is consumed) */ while( this->dstBufReadyToFlush ) { err = pthread_cond_wait(&this->condDst, &this->mutxDst); assert(!err); } LOGD("encoder - src did report EOF. Therefore report EOF too\n"); this->dstBufReadyToFlush = &endOfFile; err = pthread_cond_broadcast(&this->condDst); assert(!err); err = pthread_mutex_unlock(&this->mutxDst); assert(!err); err = 0; goto resolveWithErr; }else{ err = pthread_mutex_unlock(&this->mutxDst); assert(!err); } goto awaitBuffers; }onSrcBufDrained:{ err = pthread_mutex_lock(&this->mutxSrc); assert(!err); while( this->srcBufReadyToFill ) { pthread_cond_wait(&this->condSrc, &this->mutxSrc); } this->srcBufReadyToFill = BUF_SRC; LEN_SRC = 0; BUF_SRC = NULL; err = pthread_cond_broadcast(&this->condSrc); assert(!err); err = pthread_mutex_unlock(&this->mutxSrc); assert(!err); goto awaitBuffers; }/*update buffer states*/{ err = pthread_mutex_lock(&this->mutxDst); assert(!err); LEN_DST = iDst; BUF_DST = NULL; err = pthread_mutex_unlock(&this->mutxDst); assert(!err); }{ err = pthread_mutex_lock(&this->mutxSrc); assert(!err); LEN_SRC = 0; BUF_SRC = NULL; err = pthread_mutex_unlock(&this->mutxSrc); assert(!err); goto awaitBuffers; }resolveWithErr:{ assert(err == 0); LOGD("RETURN from %s()\n", __func__); pthread_mutex_unlock(&this->mutxSrc); return NULL; #undef BUF_DST #undef DST_CAP #undef LEN_DST #undef BUF_SRC #undef SRC_CAP #undef LEN_SRC } } INLINE(int) quotPrintDecode( App*this ){ assert(!"TODO_i5TKHBVqBa0lRhqy"); } INLINE(int) run( App*this ){ int err; /*assign initial buffers BEFORE we lanch threads, so we dont need mutx*/{ LOGD("Initial buffer assignments\n"); LOGD("srcBufReadyToFill := srcBufOne.buf\n"); LOGD("dstBufReadyToFill := dstBufOne.buf\n"); this->srcBufReadyToFill = this->srcBufOne.buf; this->dstBufReadyToFill = this->dstBufOne.buf; }/*launch a thread for processing*/{ void *(*processor)(void*) = (this->flg & FLG_isEnc) ? mainOfEncode : NULL //TODO mainOfDecode ; err = pthread_create(&this->thrdWr, NULL, processor, this); if( err ){ assert(err > 0); err = -err; goto resolveWithErr; } }/*launch a thread for reading*/{ err = pthread_create(&this->thrdRd, NULL, mainOfInputThread, this); if( err ){ assert(err > 0); err = -err; goto resolveWithErr; } }/*use main thread to do the output writing*/{ mainOfOutputThread(this); err = 0; }resolveWithErr:{ /* TODO make sure to error out if ANY of the threads had an error*/ return err; } } int main( int argc, char**argv ){ int err; int isThreadingIsInit = 0; static App app_ = { .mAGIC = App_mAGIC, }; static App*const this = &app_; { switch( parseArgs(argc, argv, this) ){ case 0: break; /*OK*/ case -1: err = 1; goto resolveWithErr; case -2: printHelp(); err = 0; goto resolveWithErr; default: assert(!"unreachable"); } }{ #define Buf_init(BUF) do{ (BUF)->len = 0; }while(0) #define Buf_fini(BUF) do{ }while(0) Buf_init(&this->dstBufOne); Buf_init(&this->dstBufTwo); Buf_init(&this->srcBufOne); Buf_init(&this->srcBufTwo); isThreadingIsInit = 1; #undef Buf_init }{ err = (run(this) != 0) * 1; goto resolveWithErr; }resolveWithErr:{ int const errBk = err; if( isThreadingIsInit ){ Buf_fini(this->dstBufOne); Buf_fini(this->dstBufTwo); Buf_fini(this->srcBufOne); Buf_fini(this->srcBufTwo); } err = errBk; assert(err == 0 || err == 1); return err; #undef Buf_fini } }