#include #include #include #include #include #include #include #define FLG_isEnc (1<<0) #define FLG_isDec (1<<1) #define FLG_isError (1<<2) #define FLG_isBinary (1<<3) #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" " About the quoted-printable encoding see:\n" " https://www.rfc-editor.org/rfc/rfc2045#section-6.7\n" " \n" " -e `encode` to QuotedPrintable\n" " -d `decode` from QuotedPrintable\n" " -b When given to `-e`, will encode `binary` safe. Eg newlines will\n" " be quoted always.\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 if( !strcmp(arg, "-b") ){ this->flg |= FLG_isBinary; }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, lineBeg = 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. */ /* TODO should we ensure that output always ends with CRLF? */ goto onDstBufFull; } for(;;){ if( iDst + 3 >= DST_CAP ) { goto onDstBufFull; } if( iSrc >= LEN_SRC ) { goto onSrcBufDrained; } uint_fast8_t c = BUF_SRC[iSrc++]; #define IS_WHITESP(C) (C == 0x09 || C == 0x0A || C == 0x0D || C == 0x20) #define NEXT_SRC_CHR (BUF_SRC[iSrc]) #define IS_NEXT_CHR_PRESENT (iSrc + 1 < LEN_SRC) #define IS_DST_SPACE_AT_LEAST(N) (iDst + (N) < DST_CAP) #define IS_BINARY_MODE ((this->flg & FLG_isBinary)==0) #define IS_EBCDIC_RISKY(C) (C == 0x21 || C == 0x23 || C == 0x24 || C == 0x34 \ || C == 0x40 || C == 0x5B || C == 0x5C || C == 0x5D || C == 0x5E || C == 0x60 \ || C == 0x7B || C == 0x7C || C == 0x7D || C == 0x7E) int const isCrLfRaw = (c == 0x0D && IS_NEXT_CHR_PRESENT && IS_DST_SPACE_AT_LEAST(2) && NEXT_SRC_CHR == 0x0A && IS_BINARY_MODE); int const isRawOk = 0 /* A CRLF sequence if a special case, as it CAN be * printed raw under some circumstances. */ || isCrLfRaw /*print as-is. Risk is pretty low to get in trouble with these*/ || (c == 0x22) || (c >= 0x25 && c <= 0x33) || (c >= 0x35 && c <= 0x3C) || (c >= 0x3E && c <= 0x3F) || (c >= 0x41 && c <= 0x5A) || (c >= 0x5F && c <= 0x5F) || (c >= 0x61 && c <= 0x7A) /*for EBCDIC, some chars are risky. For now, we'll encode them if binary * mode given, as I do NOT want to introduce yet-another-bullshit-flag*/ || (IS_EBCDIC_RISKY(c) && !IS_BINARY_MODE) /*space MUST NOT be at endOfLine, so check some more conditions*/ || (c == 0x20 && IS_NEXT_CHR_PRESENT && IS_DST_SPACE_AT_LEAST(4) && !IS_WHITESP(NEXT_SRC_CHR)) ; #undef IS_WHITESP #undef NEXT_SRC_CHR #undef IS_NEXT_CHR_PRESENT #undef IS_DST_HAS_SPACE #undef IS_EBCDIC_RISKY int const nBytesOutput = ((isRawOk != 0) * 1 + (isRawOk == 0) * 3); int const isSoftWrapNecessary = (iDst - lineBeg >= 76 - nBytesOutput); if( isSoftWrapNecessary ){ BUF_DST[iDst++] = '='; BUF_DST[iDst++] = '\r'; BUF_DST[iDst++] = '\n'; lineBeg = iDst; } if( isRawOk ){ BUF_DST[iDst++] = c; if( isCrLfRaw ) /* we have to add the LF octet too */ { BUF_DST[iDst++] = BUF_SRC[iSrc++]; } }else{ static char const hx[] = "0123456789ABCDEF"; BUF_DST[iDst++] = '='; BUF_DST[iDst++] = hx[c >> 4]; BUF_DST[iDst++] = hx[c & 0xF]; } } 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"); /* Cited from RFC: when decoding a Quoted-Printable body, any * trailing white space on a line must be deleted, as it will * necessarily have been added by intermediate transport agents */ } 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 reading*/{ err = pthread_create(&this->thrdRd, NULL, mainOfInputThread, this); if( err ){ assert(err > 0); err = -err; goto resolveWithErr; } }/*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; } }/*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 } }