#if 0 --[===========================================================================[ How to build: apt install lua5.4 lua src/quotprint.c `# will create 'quotprint' in your current dir` How to use: ./quotprint --help ]===========================================================================] do local sh = io.popen("sh", "w") assert(sh, "Failed to launch a shell") sh:write([=[set -e \ \ && CC="${CC:-gcc}" \ && LD="${LD:-gcc}" \ && RIMRAF="${RIMRAF:-rm -rf}" \ && BINEXT= \ && CFLAGS="${CFLAGS:- -std=c99 -Wall -Werror -pedantic -O2}" \ && LDFLAGS="${LDFLAGS:- -s}" \ && TMPDIR="${TMPDIR:-/tmp}" \ \ && if test -z "$PROJECT_VERSION" ;then true \ && tag="$(git tag|grep -E '^v'|sed -E 's,^v,,'|sort -V|tail -n1)" \ && cnt="$(git rev-list --count "v${tag:?}"..HEAD)" \ && hsh="$(git rev-parse --short HEAD)" \ && if test -n "$tag" -a -n "$cnt" -a -n "$hsh" ;then true \ && PROJECT_VERSION="${tag:?}-${cnt:?}-g${hsh:?}" \ ;else true \ && PROJECT_VERSION="$(date -u +0.0.0-%Y%m%d.%H%M%S)" \ ;fi \ ;fi \ \ && ${CC:?} -c -o "${TMPDIR:?}"/QwTlrZxWlNzEvZGV src/quotprint.c ${CFLAGS?} \ && ${LD:?} -o ./quotprint"${BINEXT?}" "${TMPDIR:?}"/QwTlrZxWlNzEvZGV ${LDFLAGS?} \ && ${RIMRAF:?} "${TMPDIR:?}"/QwTlrZxWlNzEvZGV \ ]=]) local ok, how, code = sh:close() if not ok then os.exit(code) end end --[===========================================================================[ #endif #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 { char _[64]; } 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 " STR_QUOT(PROJECT_VERSION) "\n" " \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" " BUG: Bad decoding when trailing whitespaces present.\n" " Fix: Make sure input does not have trailing whitespace.\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 ){ this->flg |= FLG_isEnc|FLG_isBinary; /* fallback to encode */ } 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 } PRIVATE(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(;;){ /* MUST have some headroom at end so we can properly handle * special-cases. */ if( iDst + 8 >= 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_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 && NEXT_SRC_CHR == 0x0A && !IS_BINARY_MODE); int const isRawOk = 0 /* A CRLF sequence is 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_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 ) { err = pthread_cond_wait(&this->condSrc, &this->mutxSrc); assert(!err); } 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 } } PRIVATE(void*) mainOfDecode( void*_ ){ int err, iSrc = 0, iDst = 0; DEFINE_App(this, _); { #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("decoder - 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("decoder - 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("decoder - 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 + 1 >= DST_CAP ) { goto onDstBufFull; } if( iSrc >= LEN_SRC ) { goto onSrcBufDrained; } uint_fast8_t const CHR0 = BUF_SRC[iSrc + 0]; #define CHR1 ((uint_fast8_t)BUF_SRC[iSrc + 1]) #define CHR2 ((uint_fast8_t)BUF_SRC[iSrc + 2]) #define IS_HEX(C) (0 \ || ((C) >= '0' && (C) <= '9') \ || ((C) >= 'A' && (C) <= 'F') \ || ((C) >= 'a' && (C) <= 'f') \ ) #define IS_WHITESP(C) (C == 0x09 || C == 0x0A || C == 0x0D || C == 0x20) int const isSoftWrap = (CHR0 == '=' && (CHR1 == '\r' || CHR1 == '\n')); if( isSoftWrap ){ if( CHR1 == '\n' ){ iSrc += 2; } else if( CHR1 == '\r' && CHR2 == '\n' ){ iSrc += 3; } else if( CHR1 == '\r' ){ iSrc += 2; } continue; } // TODO /* Cited from RFC: // TODO * > when decoding a Quoted-Printable body, any trailing white space on // TODO * > a line must be deleted, as it will necessarily have been added by // TODO * > intermediate transport agents. // TODO * TODO: Below impl does not work when that whitespace crosses a buffer // TODO * boundary. */ // TODO int trailSpsEnd; // TODO for( trailSpsEnd = iSrc ;; ++trailSpsEnd){ // TODO if( trailSpsEnd >= LEN_SRC ) assert(!"TODO_wSiBu8mbRajrnTZp"); // TODO if( BUF_SRC[iSrc] == ); // TODO } if( CHR0 != '=' ){ /*just a normal char*/ BUF_DST[iDst++] = CHR0; iSrc += 1; continue; } if( !IS_HEX(CHR1) || !IS_HEX(CHR2) ){ /*corrupt garbage. TODO: Should be handled with warning*/ BUF_DST[iDst++] = CHR0; BUF_DST[iDst++] = CHR1; BUF_DST[iDst++] = CHR2; iSrc += 3; continue; } /* decode from escape */ #define NIBL_OF_HEX_CHR(R, X) do{ \ uint_fast8_t c = (X); \ if( c >= '0' && c <= '9' ){ (R) = c - '0'; break; } \ if( c >= 'A' && c <= 'F' ){ (R) = c - 'A' + 10; break; } \ if( c >= 'a' && c <= 'f' ){ (R) = c - 'a' + 10; break; } \ assert(!"TODO_KwZPRb3XfwLIGOyq"); \ }while(0) uint_fast8_t nibHi; NIBL_OF_HEX_CHR(nibHi, CHR1); uint_fast8_t nibLo; NIBL_OF_HEX_CHR(nibLo, CHR2); BUF_DST[iDst++] = (nibHi << 4) | nibLo; iSrc += 3; #undef CHR1 #undef CHR2 #undef IS_HEX #undef IS_WHITESP #undef NIBL_OF_HEX_CHR } }onSrcBufDrained:{ err = pthread_mutex_lock(&this->mutxSrc); assert(!err); while( this->srcBufReadyToFill ) { err = pthread_cond_wait(&this->condSrc, &this->mutxSrc); assert(!err); } 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; }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("decoder - dstBufReadyToFlush := dstBuf%s\n", (BUF_DST==this->dstBufOne.buf)?"One":"Two"); LOGD("decoder - dstBuf%s.len := %d\n", (BUF_DST==this->dstBufOne.buf)?"One":"Two", iDst); LOGD("decoder - 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("decoder - 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; }resolveWithErr:{ assert(err == 0); return NULL; }{ #undef BUF_DST #undef DST_CAP #undef LEN_DST #undef BUF_SRC #undef SRC_CAP #undef LEN_SRC } } 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 : 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 } } /*]===========================================================================]--*/