From 1fc7241eb595b762d8fdedd830af3202794e53f0 Mon Sep 17 00:00:00 2001 From: Mark Adler Date: Sun, 18 Jan 2015 13:24:09 -0800 Subject: [PATCH] Do not abort on inflate data error -- continue to process files. This introduces try/catch/throw for error handling throughout pigz. Each thread has its own try stack for error handling. If a throw makes it to the top try in a thread, then the entire program is aborted. Data errors caught during decoding and decompression permit the process to continue with subsequent files. --- Makefile | 17 +- pigz.c | 1072 +++++++++++++++++++++++++++++------------------------- try.c | 74 ++++ try.h | 465 +++++++++++++++++++++++ yarn.c | 13 +- yarn.h | 4 +- 6 files changed, 1138 insertions(+), 507 deletions(-) create mode 100644 try.c create mode 100644 try.h diff --git a/Makefile b/Makefile index a574a3f..6524ef3 100644 --- a/Makefile +++ b/Makefile @@ -4,14 +4,16 @@ LDFLAGS=-lz ZOPFLI=zopfli/src/zopfli/ # use gcc and gmake on Solaris -pigz: pigz.o yarn.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o +pigz: pigz.o yarn.o try.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o $(CC) $(LDFLAGS) -o pigz $^ -lpthread -lm ln -f pigz unpigz -pigz.o: pigz.c yarn.h ${ZOPFLI}deflate.h ${ZOPFLI}util.h +pigz.o: pigz.c yarn.h try.h ${ZOPFLI}deflate.h ${ZOPFLI}util.h yarn.o: yarn.c yarn.h +try.o: try.c try.h + ${ZOPFLI}deflate.o: ${ZOPFLI}deflate.c ${ZOPFLI}deflate.h ${ZOPFLI}blocksplitter.h ${ZOPFLI}lz77.h ${ZOPFLI}squeeze.h ${ZOPFLI}tree.h ${ZOPFLI}zopfli.h ${ZOPFLI}cache.h ${ZOPFLI}hash.h ${ZOPFLI}util.h ${ZOPFLI}blocksplitter.o: ${ZOPFLI}blocksplitter.c ${ZOPFLI}blocksplitter.h ${ZOPFLI}deflate.h ${ZOPFLI}lz77.h ${ZOPFLI}squeeze.h ${ZOPFLI}tree.h ${ZOPFLI}util.h ${ZOPFLI}zopfli.h ${ZOPFLI}cache.h ${ZOPFLI}hash.h @@ -32,21 +34,24 @@ ${ZOPFLI}katajainen.o: ${ZOPFLI}katajainen.c ${ZOPFLI}katajainen.h dev: pigz pigzt pigzn -pigzt: pigzt.o yarnt.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o +pigzt: pigzt.o yarnt.o try.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o $(CC) $(LDFLAGS) -o pigzt $^ -lpthread -lm -pigzt.o: pigz.c yarn.h +pigzt.o: pigz.c yarn.h try.h $(CC) $(CFLAGS) -DDEBUG -g -c -o pigzt.o pigz.c yarnt.o: yarn.c yarn.h $(CC) $(CFLAGS) -DDEBUG -g -c -o yarnt.o yarn.c -pigzn: pigzn.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o +pigzn: pigzn.o tryn.o ${ZOPFLI}deflate.o ${ZOPFLI}blocksplitter.o ${ZOPFLI}tree.o ${ZOPFLI}lz77.o ${ZOPFLI}cache.o ${ZOPFLI}hash.o ${ZOPFLI}util.o ${ZOPFLI}squeeze.o ${ZOPFLI}katajainen.o $(CC) $(LDFLAGS) -o pigzn $^ -lm -pigzn.o: pigz.c +pigzn.o: pigz.c try.h $(CC) $(CFLAGS) -DDEBUG -DNOTHREAD -g -c -o pigzn.o pigz.c +tryn.o: try.c try.h + $(CC) $(CFLAGS) -DDEBUG -DNOTHREAD -g -c -o tryn.o try.c + test: pigz ./pigz -kf pigz.c ; ./pigz -t pigz.c.gz ./pigz -kfb 32 pigz.c ; ./pigz -t pigz.c.gz diff --git a/pigz.c b/pigz.c index 000ecbd..83b5a38 100644 --- a/pigz.c +++ b/pigz.c @@ -308,7 +308,8 @@ /* atoi(), getenv() */ #include /* va_start(), va_end(), va_list */ #include /* memset(), memchr(), memcpy(), strcmp(), strcpy() */ - /* strncpy(), strlen(), strcat(), strrchr() */ + /* strncpy(), strlen(), strcat(), strrchr(), + strerror() */ #include /* errno, EEXIST */ #include /* assert() */ #include /* ctime(), time(), time_t, mktime() */ @@ -367,6 +368,8 @@ ZopfliInitOptions(), ZopfliOptions */ +#include "try.h" /* try, catch, always, throw, drop, punt, ball_t */ + /* for local functions and globals */ #define local static @@ -524,16 +527,6 @@ local int complain(char *fmt, ...) return 0; } -/* exit with error, delete output file if in the middle of writing it */ -local int bail(char *why, char *what) -{ - if (g.outd != -1 && g.outf != NULL) - unlink(g.outf); - complain("abort: %s%s", why, what); - exit(1); - return 0; -} - #ifdef DEBUG /* memory tracking */ @@ -635,6 +628,30 @@ local void zlib_free(voidpf opaque, voidpf address) #define ZALLOC zlib_alloc #define ZFREE zlib_free +#else /* !DEBUG */ + +#define MALLOC malloc +#define REALLOC realloc +#define FREE free +#define OPAQUE Z_NULL +#define ZALLOC Z_NULL +#define ZFREE Z_NULL + +#endif + +/* assured memory allocation */ +local void *alloc(void *ptr, size_t size) +{ + ptr = REALLOC(ptr, size); + if (ptr == NULL) + throw(ENOMEM, "not enough memory"); + return ptr; +} + +#if DEBUG + +/* logging */ + /* starting time of day for tracing */ local struct timeval start; @@ -677,18 +694,12 @@ local void log_add(char *fmt, ...) char msg[MAXMSG]; gettimeofday(&now, NULL); - me = MALLOC(sizeof(struct log)); - if (me == NULL) - bail("not enough memory", ""); + me = alloc(NULL, sizeof(struct log)); me->when = now; va_start(ap, fmt); vsnprintf(msg, MAXMSG, fmt, ap); va_end(ap); - me->msg = MALLOC(strlen(msg) + 1); - if (me->msg == NULL) { - FREE(me); - bail("not enough memory", ""); - } + me->msg = alloc(NULL, strlen(msg) + 1); strcpy(me->msg, msg); me->next = NULL; #ifndef NOTHREAD @@ -790,18 +801,31 @@ local void log_dump(void) #else /* !DEBUG */ -#define MALLOC malloc -#define REALLOC realloc -#define FREE free -#define OPAQUE Z_NULL -#define ZALLOC Z_NULL -#define ZFREE Z_NULL - #define log_dump() #define Trace(x) #endif +/* abort or catch termination signal */ +local void cut_short(int sig) +{ + if (sig == SIGINT) + Trace(("termination by user")); + if (g.outd != -1 && g.outf != NULL) + unlink(g.outf); + RELEASE(g.outf); + log_dump(); + _exit(sig < 0 ? -sig : ECANCELED); +} + +/* common code for catch block of top routine in the thread */ +#define THREADABORT(ball) \ + do { \ + complain("abort: %s", (ball).why); \ + drop(ball); \ + cut_short(-(ball).code); \ + } while (0) + /* compute next size up by multiplying by about 2**(1/3) and rounding to the next power of 2 if close (three applications results in doubling) -- if small, go up to at least 16, if overflow, go to max size_t value */ @@ -830,23 +854,17 @@ local inline size_t vmemcpy(char **mem, size_t *size, size_t off, void *cpy, size_t len) { size_t need; - char *bigger; need = off + len; if (need < off) - bail("overflow", ""); + throw(EOVERFLOW, "overflow"); if (need > *size) { need = grow(need); - if (off) - bigger = REALLOC(*mem, need); - else { + if (off == 0) { RELEASE(*mem); *size = 0; - bigger = MALLOC(need); } - if (bigger == NULL) - bail("not enough memory", ""); - *mem = bigger; + *mem = alloc(*mem, need); *size = need; } memcpy(*mem + off, cpy, len); @@ -871,7 +889,7 @@ local size_t readn(int desc, unsigned char *buf, size_t len) while (len) { ret = read(desc, buf, len); if (ret < 0) - bail("read error on ", g.inf); + throw(errno, "read error on %s (%s)", g.inf, strerror(errno)); if (ret == 0) break; buf += ret; @@ -888,10 +906,8 @@ local void writen(int desc, unsigned char *buf, size_t len) while (len) { ret = write(desc, buf, len); - if (ret < 1) { - complain("write error code %d", errno); - bail("write error on ", g.outf); - } + if (ret < 1) + throw(errno, "write error on %s (%s)", g.outf, strerror(errno)); buf += ret; len -= ret; } @@ -1246,13 +1262,9 @@ local struct space *get_space(struct pool *pool) pool->limit--; pool->made++; release(pool->have); - space = MALLOC(sizeof(struct space)); - if (space == NULL) - bail("not enough memory", ""); + space = alloc(NULL, sizeof(struct space)); space->use = new_lock(1); /* initially one user */ - space->buf = MALLOC(pool->size); - if (space->buf == NULL) - bail("not enough memory", ""); + space->buf = alloc(NULL, pool->size); space->size = pool->size; space->len = 0; space->pool = pool; /* remember the pool this belongs to */ @@ -1267,12 +1279,10 @@ local void grow_space(struct space *space) /* compute next size up */ more = grow(space->size); if (more == space->size) - bail("not enough memory", ""); + throw(EOVERFLOW, "overflow"); /* reallocate the buffer */ - space->buf = REALLOC(space->buf, more); - if (space->buf == NULL) - bail("not enough memory", ""); + space->buf = alloc(space->buf, more); space->size = more; } @@ -1290,6 +1300,8 @@ local void drop_space(struct space *space) int use; struct pool *pool; + if (space == NULL) + return; possess(space->use); use = peek_lock(space->use); assert(use != 0); @@ -1462,214 +1474,223 @@ local void compress_thread(void *dummy) int bits; /* deflate pending bits */ #endif struct space *temp = NULL; /* temporary space for zopfli input */ + int ret; /* zlib return code */ z_stream strm; /* deflate stream */ + ball_t err; /* error information from throw() */ (void)dummy; - /* initialize the deflate stream for this thread */ - strm.zfree = ZFREE; - strm.zalloc = ZALLOC; - strm.opaque = OPAQUE; - if (deflateInit2(&strm, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) - bail("not enough memory", ""); - - /* keep looking for work */ - for (;;) { - /* get a job (like I tell my son) */ - possess(compress_have); - wait_for(compress_have, NOT_TO_BE, 0); - job = compress_head; - assert(job != NULL); - if (job->seq == -1) - break; - compress_head = job->next; - if (job->next == NULL) - compress_tail = &compress_head; - twist(compress_have, BY, -1); - - /* got a job -- initialize and set the compression level (note that if - deflateParams() is called immediately after deflateReset(), there is - no need to initialize the input/output for the stream) */ - Trace(("-- compressing #%ld", job->seq)); - if (g.level <= 9) { - (void)deflateReset(&strm); - (void)deflateParams(&strm, g.level, Z_DEFAULT_STRATEGY); - } - else { - temp = get_space(&out_pool); - temp->len = 0; - } - - /* set dictionary if provided, release that input or dictionary buffer - (not NULL if g.setdict is true and if this is not the first work - unit) */ - if (job->out != NULL) { - len = job->out->len; - left = len < DICT ? len : DICT; - if (g.level <= 9) - deflateSetDictionary(&strm, job->out->buf + (len - left), - left); + try { + /* initialize the deflate stream for this thread */ + strm.zfree = ZFREE; + strm.zalloc = ZALLOC; + strm.opaque = OPAQUE; + ret = deflateInit2(&strm, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + if (ret == Z_MEM_ERROR) + throw(ENOMEM, "not enough memory"); + if (ret != Z_OK) + throw(EINVAL, "internal error"); + + /* keep looking for work */ + for (;;) { + /* get a job (like I tell my son) */ + possess(compress_have); + wait_for(compress_have, NOT_TO_BE, 0); + job = compress_head; + assert(job != NULL); + if (job->seq == -1) + break; + compress_head = job->next; + if (job->next == NULL) + compress_tail = &compress_head; + twist(compress_have, BY, -1); + + /* got a job -- initialize and set the compression level (note that + if deflateParams() is called immediately after deflateReset(), + there is no need to initialize input/output for the stream) */ + Trace(("-- compressing #%ld", job->seq)); + if (g.level <= 9) { + (void)deflateReset(&strm); + (void)deflateParams(&strm, g.level, Z_DEFAULT_STRATEGY); + } else { - memcpy(temp->buf, job->out->buf + (len - left), left); - temp->len = left; + if (temp == NULL) + temp = get_space(&out_pool); + temp->len = 0; } - drop_space(job->out); - } - - /* set up input and output */ - job->out = get_space(&out_pool); - if (g.level <= 9) { - strm.next_in = job->in->buf; - strm.next_out = job->out->buf; - } - else - memcpy(temp->buf + temp->len, job->in->buf, job->in->len); - /* compress each block, either flushing or finishing */ - next = job->lens == NULL ? NULL : job->lens->buf; - left = job->in->len; - job->out->len = 0; - do { - /* decode next block length from blocks list */ - len = next == NULL ? 128 : *next++; - if (len < 128) /* 64..32831 */ - len = (len << 8) + (*next++) + 64; - else if (len == 128) /* end of list */ - len = left; - else if (len < 192) /* 1..63 */ - len &= 0x3f; - else if (len < 224){ /* 32832..2129983 */ - len = ((len & 0x1f) << 16) + (*next++ << 8); - len += *next++ + 32832U; - } - else { /* 2129984..539000895 */ - len = ((len & 0x1f) << 24) + (*next++ << 16); - len += *next++ << 8; - len += *next++ + 2129984UL; + /* set dictionary if provided, release that input or dictionary + buffer (not NULL if g.setdict is true and if this is not the + first work unit) */ + if (job->out != NULL) { + len = job->out->len; + left = len < DICT ? len : DICT; + if (g.level <= 9) + deflateSetDictionary(&strm, job->out->buf + (len - left), + left); + else { + memcpy(temp->buf, job->out->buf + (len - left), left); + temp->len = left; + } + drop_space(job->out); } - left -= len; + /* set up input and output */ + job->out = get_space(&out_pool); if (g.level <= 9) { - /* run MAXP2-sized amounts of input through deflate -- this - loop is needed for those cases where the unsigned type is - smaller than the size_t type, or when len is close to the - limit of the size_t type */ - while (len > MAXP2) { - strm.avail_in = MAXP2; - deflate_engine(&strm, job->out, Z_NO_FLUSH); - len -= MAXP2; + strm.next_in = job->in->buf; + strm.next_out = job->out->buf; + } + else + memcpy(temp->buf + temp->len, job->in->buf, job->in->len); + + /* compress each block, either flushing or finishing */ + next = job->lens == NULL ? NULL : job->lens->buf; + left = job->in->len; + job->out->len = 0; + do { + /* decode next block length from blocks list */ + len = next == NULL ? 128 : *next++; + if (len < 128) /* 64..32831 */ + len = (len << 8) + (*next++) + 64; + else if (len == 128) /* end of list */ + len = left; + else if (len < 192) /* 1..63 */ + len &= 0x3f; + else if (len < 224){ /* 32832..2129983 */ + len = ((len & 0x1f) << 16) + (*next++ << 8); + len += *next++ + 32832U; } + else { /* 2129984..539000895 */ + len = ((len & 0x1f) << 24) + (*next++ << 16); + len += *next++ << 8; + len += *next++ + 2129984UL; + } + left -= len; + + if (g.level <= 9) { + /* run MAXP2-sized amounts of input through deflate -- this + loop is needed for those cases where the unsigned type + is smaller than the size_t type, or when len is close to + the limit of the size_t type */ + while (len > MAXP2) { + strm.avail_in = MAXP2; + deflate_engine(&strm, job->out, Z_NO_FLUSH); + len -= MAXP2; + } - /* run the last piece through deflate -- end on a byte - boundary, using a sync marker if necessary, or finish the - deflate stream if this is the last block */ - strm.avail_in = (unsigned)len; - if (left || job->more) { + /* run the last piece through deflate -- end on a byte + boundary, using a sync marker if necessary, or finish + the deflate stream if this is the last block */ + strm.avail_in = (unsigned)len; + if (left || job->more) { #if ZLIB_VERNUM >= 0x1260 - deflate_engine(&strm, job->out, Z_BLOCK); - - /* add enough empty blocks to get to a byte boundary */ - (void)deflatePending(&strm, Z_NULL, &bits); - if (bits & 1) - deflate_engine(&strm, job->out, Z_SYNC_FLUSH); - else if (bits & 7) { - do { /* add static empty blocks */ - bits = deflatePrime(&strm, 10, 2); - assert(bits == Z_OK); - (void)deflatePending(&strm, Z_NULL, &bits); - } while (bits & 7); deflate_engine(&strm, job->out, Z_BLOCK); - } + + /* add enough empty blocks to get to a byte boundary */ + (void)deflatePending(&strm, Z_NULL, &bits); + if (bits & 1) + deflate_engine(&strm, job->out, Z_SYNC_FLUSH); + else if (bits & 7) { + do { /* add static empty blocks */ + bits = deflatePrime(&strm, 10, 2); + assert(bits == Z_OK); + (void)deflatePending(&strm, Z_NULL, &bits); + } while (bits & 7); + deflate_engine(&strm, job->out, Z_BLOCK); + } #else - deflate_engine(&strm, job->out, Z_SYNC_FLUSH); + deflate_engine(&strm, job->out, Z_SYNC_FLUSH); #endif + } + else + deflate_engine(&strm, job->out, Z_FINISH); } - else - deflate_engine(&strm, job->out, Z_FINISH); - } - else { - /* compress len bytes using zopfli, bring to byte boundary */ - unsigned char bits, *out; - size_t outsize; - - out = NULL; - outsize = 0; - bits = 0; - ZopfliDeflatePart(&g.zopts, 2, !(left || job->more), - temp->buf, temp->len, temp->len + len, - &bits, &out, &outsize); - assert(job->out->len + outsize + 5 <= job->out->size); - memcpy(job->out->buf + job->out->len, out, outsize); - free(out); - job->out->len += outsize; - if (left || job->more) { - bits &= 7; - if (bits & 1) { - if (bits == 7) + else { + /* compress len bytes using zopfli, end at byte boundary */ + unsigned char bits, *out; + size_t outsize; + + out = NULL; + outsize = 0; + bits = 0; + ZopfliDeflatePart(&g.zopts, 2, !(left || job->more), + temp->buf, temp->len, temp->len + len, + &bits, &out, &outsize); + assert(job->out->len + outsize + 5 <= job->out->size); + memcpy(job->out->buf + job->out->len, out, outsize); + free(out); + job->out->len += outsize; + if (left || job->more) { + bits &= 7; + if (bits & 1) { + if (bits == 7) + job->out->buf[job->out->len++] = 0; job->out->buf[job->out->len++] = 0; - job->out->buf[job->out->len++] = 0; - job->out->buf[job->out->len++] = 0; - job->out->buf[job->out->len++] = 0xff; - job->out->buf[job->out->len++] = 0xff; - } - else if (bits) { - do { - job->out->buf[job->out->len - 1] += 2 << bits; job->out->buf[job->out->len++] = 0; - bits += 2; - } while (bits < 8); + job->out->buf[job->out->len++] = 0xff; + job->out->buf[job->out->len++] = 0xff; + } + else if (bits) { + do { + job->out->buf[job->out->len - 1] += 2 << bits; + job->out->buf[job->out->len++] = 0; + bits += 2; + } while (bits < 8); + } } + temp->len += len; } - temp->len += len; - } - } while (left); - if (g.level > 9) - drop_space(temp); - if (job->lens != NULL) { + } while (left); drop_space(job->lens); job->lens = NULL; - } - Trace(("-- compressed #%ld%s", job->seq, job->more ? "" : " (last)")); + Trace(("-- compressed #%ld%s", job->seq, + job->more ? "" : " (last)")); - /* reserve input buffer until check value has been calculated */ - use_space(job->in); + /* reserve input buffer until check value has been calculated */ + use_space(job->in); - /* insert write job in list in sorted order, alert write thread */ - possess(write_first); - prior = &write_head; - while ((here = *prior) != NULL) { - if (here->seq > job->seq) - break; - prior = &(here->next); - } - job->next = here; - *prior = job; - twist(write_first, TO, write_head->seq); - - /* calculate the check value in parallel with writing, alert the write - thread that the calculation is complete, and drop this usage of the - input buffer */ - len = job->in->len; - next = job->in->buf; - check = CHECK(0L, Z_NULL, 0); - while (len > MAXP2) { - check = CHECK(check, next, MAXP2); - len -= MAXP2; - next += MAXP2; + /* insert write job in list in sorted order, alert write thread */ + possess(write_first); + prior = &write_head; + while ((here = *prior) != NULL) { + if (here->seq > job->seq) + break; + prior = &(here->next); + } + job->next = here; + *prior = job; + twist(write_first, TO, write_head->seq); + + /* calculate the check value in parallel with writing, alert the + write thread that the calculation is complete, and drop this + usage of the input buffer */ + len = job->in->len; + next = job->in->buf; + check = CHECK(0L, Z_NULL, 0); + while (len > MAXP2) { + check = CHECK(check, next, MAXP2); + len -= MAXP2; + next += MAXP2; + } + check = CHECK(check, next, (unsigned)len); + drop_space(job->in); + job->check = check; + Trace(("-- checked #%ld%s", job->seq, job->more ? "" : " (last)")); + possess(job->calc); + twist(job->calc, TO, 1); + + /* done with that one -- go find another job */ } - check = CHECK(check, next, (unsigned)len); - drop_space(job->in); - job->check = check; - Trace(("-- checked #%ld%s", job->seq, job->more ? "" : " (last)")); - possess(job->calc); - twist(job->calc, TO, 1); - /* done with that one -- go find another job */ + /* found job with seq == -1 -- return to join */ + drop_space(temp); + release(compress_have); + (void)deflateEnd(&strm); + } + catch (err) { + THREADABORT(err); } - - /* found job with seq == -1 -- free deflate memory and return to join */ - release(compress_have); - (void)deflateEnd(&strm); } /* collect the write jobs off of the list in sequence order and write out the @@ -1685,63 +1706,69 @@ local void write_thread(void *dummy) unsigned long ulen; /* total uncompressed size (overflow ok) */ unsigned long clen; /* total compressed size (overflow ok) */ unsigned long check; /* check value of uncompressed data */ + ball_t err; /* error information from throw() */ (void)dummy; - /* build and write header */ - Trace(("-- write thread running")); - head = put_header(); + try { + /* build and write header */ + Trace(("-- write thread running")); + head = put_header(); - /* process output of compress threads until end of input */ - ulen = clen = 0; - check = CHECK(0L, Z_NULL, 0); - seq = 0; - do { - /* get next write job in order */ - possess(write_first); - wait_for(write_first, TO_BE, seq); - job = write_head; - write_head = job->next; - twist(write_first, TO, write_head == NULL ? -1 : write_head->seq); - - /* update lengths, save uncompressed length for COMB */ - more = job->more; - len = job->in->len; - drop_space(job->in); - ulen += (unsigned long)len; - clen += (unsigned long)(job->out->len); - - /* write the compressed data and drop the output buffer */ - Trace(("-- writing #%ld", seq)); - writen(g.outd, job->out->buf, job->out->len); - drop_space(job->out); - Trace(("-- wrote #%ld%s", seq, more ? "" : " (last)")); - - /* wait for check calculation to complete, then combine, once - the compress thread is done with the input, release it */ - possess(job->calc); - wait_for(job->calc, TO_BE, 1); - release(job->calc); - check = COMB(check, job->check, len); - - /* free the job */ - free_lock(job->calc); - FREE(job); - - /* get the next buffer in sequence */ - seq++; - } while (more); + /* process output of compress threads until end of input */ + ulen = clen = 0; + check = CHECK(0L, Z_NULL, 0); + seq = 0; + do { + /* get next write job in order */ + possess(write_first); + wait_for(write_first, TO_BE, seq); + job = write_head; + write_head = job->next; + twist(write_first, TO, write_head == NULL ? -1 : write_head->seq); + + /* update lengths, save uncompressed length for COMB */ + more = job->more; + len = job->in->len; + drop_space(job->in); + ulen += (unsigned long)len; + clen += (unsigned long)(job->out->len); + + /* write the compressed data and drop the output buffer */ + Trace(("-- writing #%ld", seq)); + writen(g.outd, job->out->buf, job->out->len); + drop_space(job->out); + Trace(("-- wrote #%ld%s", seq, more ? "" : " (last)")); - /* write trailer */ - put_trailer(ulen, clen, check, head); + /* wait for check calculation to complete, then combine, once + the compress thread is done with the input, release it */ + possess(job->calc); + wait_for(job->calc, TO_BE, 1); + release(job->calc); + check = COMB(check, job->check, len); - /* verify no more jobs, prepare for next use */ - possess(compress_have); - assert(compress_head == NULL && peek_lock(compress_have) == 0); - release(compress_have); - possess(write_first); - assert(write_head == NULL); - twist(write_first, TO, -1); + /* free the job */ + free_lock(job->calc); + FREE(job); + + /* get the next buffer in sequence */ + seq++; + } while (more); + + /* write trailer */ + put_trailer(ulen, clen, check, head); + + /* verify no more jobs, prepare for next use */ + possess(compress_have); + assert(compress_head == NULL && peek_lock(compress_have) == 0); + release(compress_have); + possess(write_first); + assert(write_head == NULL); + twist(write_first, TO, -1); + } + catch (err) { + THREADABORT(err); + } } /* encode a hash hit to the block lengths list -- hit == 0 ends the list */ @@ -1815,9 +1842,7 @@ local void parallel_compress(void) left = 0; do { /* create a new job */ - job = MALLOC(sizeof(struct job)); - if (job == NULL) - bail("not enough memory", ""); + job = alloc(NULL, sizeof(struct job)); job->calc = new_lock(0); /* update input spaces */ @@ -1935,7 +1960,7 @@ local void parallel_compress(void) job->seq = seq; Trace(("-- read #%ld%s", seq, more ? "" : " (last)")); if (++seq < 1) - bail("input too long: ", g.inf); + throw(EOVERFLOW, "overflow"); /* start another compress thread if needed */ if (cthreads < seq && cthreads < g.procs) { @@ -2011,18 +2036,21 @@ local void single_compress(int reset) /* initialize the deflate structure if this is the first time */ if (strm == NULL) { + int ret; /* zlib return code */ + out_size = g.block > MAXP2 ? MAXP2 : (unsigned)g.block; - if ((in = MALLOC(g.block + DICT)) == NULL || - (next = MALLOC(g.block + DICT)) == NULL || - (out = MALLOC(out_size)) == NULL || - (strm = MALLOC(sizeof(z_stream))) == NULL) - bail("not enough memory", ""); + in = alloc(NULL, g.block + DICT); + next = alloc(NULL, g.block + DICT); + out = alloc(NULL, out_size); + strm = alloc(NULL, sizeof(z_stream)); strm->zfree = ZFREE; strm->zalloc = ZALLOC; strm->opaque = OPAQUE; - if (deflateInit2(strm, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != - Z_OK) - bail("not enough memory", ""); + ret = deflateInit2(strm, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + if (ret == Z_MEM_ERROR) + throw(ENOMEM, "not enough memory"); + if (ret != Z_OK) + throw(EINVAL, "internal error"); } /* write header */ @@ -2219,17 +2247,24 @@ local void single_compress(int reset) local void load_read(void *dummy) { size_t len; + ball_t err; /* error information from throw() */ (void)dummy; Trace(("-- launched decompress read thread")); - do { - possess(g.load_state); - wait_for(g.load_state, TO_BE, 1); - g.in_len = len = readn(g.ind, g.in_which ? g.in_buf : g.in_buf2, BUF); - Trace(("-- decompress read thread read %lu bytes", len)); - twist(g.load_state, TO, 0); - } while (len == BUF); + try { + do { + possess(g.load_state); + wait_for(g.load_state, TO_BE, 1); + g.in_len = len = readn(g.ind, g.in_which ? g.in_buf : g.in_buf2, + BUF); + Trace(("-- decompress read thread read %lu bytes", len)); + twist(g.load_state, TO, 0); + } while (len == BUF); + } + catch (err) { + THREADABORT(err); + } Trace(("-- exited decompress read thread")); } #endif @@ -2482,9 +2517,7 @@ local int get_header(int save) fname = GET2(); extra = GET2(); if (save) { - char *next = g.hname = MALLOC(fname + 1); - if (g.hname == NULL) - bail("not enough memory", ""); + char *next = g.hname = alloc(NULL, fname + 1); while (fname > g.in_left) { memcpy(next, g.in_next, g.in_left); fname -= g.in_left; @@ -2709,7 +2742,7 @@ local void list_info(void) if (method < 0) { RELEASE(g.hname); if (method != -1 && g.verbosity > 1) - complain("%s not a compressed file -- skipping", g.inf); + complain("skipping: %s not a compressed file", g.inf); return; } @@ -2760,7 +2793,7 @@ local void list_info(void) /* skip to end to get trailer (8 bytes), compute compressed length */ if (g.in_short) { /* whole thing already read */ if (g.in_left < 8) { - complain("%s not a valid gzip file -- skipping", g.inf); + complain("skipping: %s not a valid gzip file", g.inf); return; } g.in_tot = g.in_left - 8; /* compressed size */ @@ -2779,7 +2812,7 @@ local void list_info(void) } while (g.in_left == BUF); /* read until end */ if (g.in_left < 8) { if (n + g.in_left < 8) { - complain("%s not a valid gzip file -- skipping", g.inf); + complain("skipping: %s not a valid gzip file", g.inf); return; } if (g.in_left) { @@ -2793,7 +2826,7 @@ local void list_info(void) g.in_tot -= at + 8; } if (g.in_tot < 2) { - complain("%s not a valid gzip file -- skipping", g.inf); + complain("skipping: %s not a valid gzip file", g.inf); return; } @@ -2853,19 +2886,25 @@ local lock *outb_check_more; local void outb_write(void *dummy) { size_t len; + ball_t err; /* error information from throw() */ (void)dummy; Trace(("-- launched decompress write thread")); - do { - possess(outb_write_more); - wait_for(outb_write_more, TO_BE, 1); - len = out_len; - if (len && g.decode == 1) - writen(g.outd, out_copy, len); - Trace(("-- decompress wrote %lu bytes", len)); - twist(outb_write_more, TO, 0); - } while (len); + try { + do { + possess(outb_write_more); + wait_for(outb_write_more, TO_BE, 1); + len = out_len; + if (len && g.decode == 1) + writen(g.outd, out_copy, len); + Trace(("-- decompress wrote %lu bytes", len)); + twist(outb_write_more, TO, 0); + } while (len); + } + catch (err) { + THREADABORT(err); + } Trace(("-- exited decompress write thread")); } @@ -2873,18 +2912,24 @@ local void outb_write(void *dummy) local void outb_check(void *dummy) { size_t len; + ball_t err; /* error information from throw() */ (void)dummy; Trace(("-- launched decompress check thread")); - do { - possess(outb_check_more); - wait_for(outb_check_more, TO_BE, 1); - len = out_len; - g.out_check = CHECK(g.out_check, out_copy, len); - Trace(("-- decompress checked %lu bytes", len)); - twist(outb_check_more, TO, 0); - } while (len); + try { + do { + possess(outb_check_more); + wait_for(outb_check_more, TO_BE, 1); + len = out_len; + g.out_check = CHECK(g.out_check, out_copy, len); + Trace(("-- decompress checked %lu bytes", len)); + twist(outb_check_more, TO, 0); + } while (len); + } + catch (err) { + THREADABORT(err); + } Trace(("-- exited decompress check thread")); } #endif @@ -2972,18 +3017,25 @@ local void infchk(void) strm.zfree = ZFREE; strm.opaque = OPAQUE; ret = inflateBackInit(&strm, 15, out_buf); + if (ret == Z_MEM_ERROR) + throw(ENOMEM, "not enough memory"); if (ret != Z_OK) - bail("not enough memory", ""); + throw(EINVAL, "internal error"); /* decompress, compute lengths and check value */ strm.avail_in = g.in_left; strm.next_in = g.in_next; ret = inflateBack(&strm, inb, NULL, outb, NULL); + inflateBackEnd(&strm); + if (ret == Z_DATA_ERROR) + throw(EFTYPE, "%s: corrupted -- invalid deflate data (%s)", + g.inf, strm.msg); + if (ret == Z_BUF_ERROR) + throw(EFTYPE, "%s: corrupted -- incomplete deflate data", g.inf); if (ret != Z_STREAM_END) - bail("corrupted input -- invalid deflate data: ", g.inf); + throw(EINVAL, "internal error"); g.in_left = strm.avail_in; g.in_next = strm.next_in; - inflateBackEnd(&strm); outb(NULL, NULL, 0); /* finish off final write and check */ /* compute compressed data length */ @@ -2997,12 +3049,14 @@ local void infchk(void) g.zip_clen = GET4(); g.zip_ulen = GET4(); if (g.in_eof) - bail("corrupted zip entry -- missing trailer: ", g.inf); + throw(EFTYPE, "%s: corrupted entry -- missing trailer", + g.inf); /* if crc doesn't match, try info-zip variant with sig */ if (g.zip_crc != g.out_check) { if (g.zip_crc != 0x08074b50UL || g.zip_clen != g.out_check) - bail("corrupted zip entry -- crc32 mismatch: ", g.inf); + throw(EFTYPE, "%s: corrupted entry -- crc32 mismatch", + g.inf); g.zip_crc = g.zip_clen; g.zip_clen = g.zip_ulen; g.zip_ulen = GET4(); @@ -3024,11 +3078,13 @@ local void infchk(void) (void)GET4(); } if (g.in_eof) - bail("corrupted zip entry -- missing trailer: ", g.inf); + throw(EFTYPE, "%s: corrupted entry -- missing trailer", + g.inf); } if (g.zip_clen != (clen & LOW32) || g.zip_ulen != (g.out_tot & LOW32)) - bail("corrupted zip entry -- length mismatch: ", g.inf); + throw(EFTYPE, "%s: corrupted entry -- length mismatch", + g.inf); check = g.zip_crc; } else if (g.form == 1) { /* zlib (big-endian) trailer */ @@ -3037,19 +3093,19 @@ local void infchk(void) check += (unsigned)(GET()) << 8; check += GET(); if (g.in_eof) - bail("corrupted zlib stream -- missing trailer: ", g.inf); + throw(EFTYPE, "%s: corrupted -- missing trailer", g.inf); if (check != g.out_check) - bail("corrupted zlib stream -- adler32 mismatch: ", g.inf); + throw(EFTYPE, "%s: corrupted -- adler32 mismatch", g.inf); } else { /* gzip trailer */ check = GET4(); len = GET4(); if (g.in_eof) - bail("corrupted gzip stream -- missing trailer: ", g.inf); + throw(EFTYPE, "%s: corrupted -- missing trailer", g.inf); if (check != g.out_check) - bail("corrupted gzip stream -- crc32 mismatch: ", g.inf); + throw(EFTYPE, "%s: corrupted -- crc32 mismatch", g.inf); if (len != (g.out_tot & LOW32)) - bail("corrupted gzip stream -- length mismatch: ", g.inf); + throw(EFTYPE, "%s: corrupted -- length mismatch", g.inf); } /* show file information if requested */ @@ -3069,9 +3125,9 @@ local void infchk(void) !g.list) cat(); else if (was > 1 && get_header(0) != -5) - complain("entries after the first in %s were ignored", g.inf); + complain("warning: %s: entries after the first were ignored", g.inf); else if ((was == 0 && ret != -1) || (was == 1 && (GET(), !g.in_eof))) - complain("%s OK, has trailing junk which was ignored", g.inf); + complain("warning: %s: trailing junk was ignored", g.inf); } /* --- decompress Unix compress (LZW) input --- */ @@ -3110,13 +3166,13 @@ local void unlzw(void) /* process remainder of compress header -- a flags byte */ g.out_tot = 0; if (NOMORE()) - bail("lzw premature end: ", g.inf); + throw(EFTYPE, "%s: lzw premature end", g.inf); flags = NEXT(); if (flags & 0x60) - bail("unknown lzw flags set: ", g.inf); + throw(EFTYPE, "%s: unknown lzw flags set", g.inf); max = flags & 0x1f; if (max < 9 || max > 16) - bail("lzw bits out of range: ", g.inf); + throw(EFTYPE, "%s: lzw bits out of range", g.inf); if (max == 9) /* 9 doesn't really mean 9 */ max = 10; flags &= 0x80; /* true if block compress */ @@ -3135,13 +3191,13 @@ local void unlzw(void) return; buf = NEXT(); if (NOMORE()) - bail("lzw premature end: ", g.inf); /* need at least nine bits */ + throw(EFTYPE, "%s: lzw premature end", g.inf); /* need nine bits */ buf += NEXT() << 8; final = prev = buf & mask; /* code */ buf >>= bits; left = 16 - bits; if (prev > 255) - bail("invalid lzw code: ", g.inf); + throw(EFTYPE, "%s: invalid lzw code", g.inf); out_buf[0] = final; /* write first decompressed byte */ outcnt = 1; @@ -3185,7 +3241,7 @@ local void unlzw(void) left += 8; if (left < bits) { if (NOMORE()) - bail("lzw premature end: ", g.inf); + throw(EFTYPE, "%s: lzw premature end", g.inf); buf += (bits_t)(NEXT()) << left; left += 8; } @@ -3229,7 +3285,7 @@ local void unlzw(void) code we drop through (prev) will be a valid index so that random input does not cause an exception. */ if (code != end + 1 || prev > end) - bail("invalid lzw code: ", g.inf); + throw(EFTYPE, "%s: invalid lzw code", g.inf); match[stack++] = final; code = prev; } @@ -3330,6 +3386,7 @@ local void process(char *path) int method = -1; /* get_header() return value */ size_t len; /* length of base name (minus suffix) */ struct stat st; /* to get file type and mod time */ + ball_t err; /* error information from throw() */ /* all compressed suffixes for decoding search, in length order */ static char *sufs[] = {".z", "-z", "_z", ".Z", ".gz", "-gz", ".zz", "-zz", ".zip", ".ZIP", ".tgz", NULL}; @@ -3363,12 +3420,12 @@ local void process(char *path) } #ifdef EOVERFLOW if (errno == EOVERFLOW || errno == EFBIG) - bail(g.inf, - " too large -- not compiled with large file support"); + throw(EFTYPE, "%s too large -- " + "not compiled with large file support", g.inf); #endif if (errno) { g.inf[len] = 0; - complain("%s does not exist -- skipping", g.inf); + complain("skipping: %s does not exist", g.inf); return; } len = strlen(g.inf); @@ -3379,15 +3436,15 @@ local void process(char *path) if ((st.st_mode & S_IFMT) != S_IFREG && (st.st_mode & S_IFMT) != S_IFLNK && (st.st_mode & S_IFMT) != S_IFDIR) { - complain("%s is a special file or device -- skipping", g.inf); + complain("skipping: %s is a special file or device", g.inf); return; } if ((st.st_mode & S_IFMT) == S_IFLNK && !g.force && !g.pipeout) { - complain("%s is a symbolic link -- skipping", g.inf); + complain("skipping: %s is a symbolic link", g.inf); return; } if ((st.st_mode & S_IFMT) == S_IFDIR && !g.recurse) { - complain("%s is a directory -- skipping", g.inf); + complain("skipping: %s is a directory", g.inf); return; } @@ -3430,7 +3487,7 @@ local void process(char *path) /* don't compress .gz (or provided suffix) files, unless -f */ if (!(g.force || g.list || g.decode) && len >= strlen(g.sufx) && strcmp(g.inf + len - strlen(g.sufx), g.sufx) == 0) { - complain("%s ends with %s -- skipping", g.inf, g.sufx); + complain("skipping: %s ends with %s", g.inf, g.sufx); return; } @@ -3438,7 +3495,7 @@ local void process(char *path) if (g.decode == 1 && !g.pipeout && !g.list) { int suf = compressed_suffix(g.inf); if (suf == 0) { - complain("%s does not have compressed suffix -- skipping", + complain("skipping: %s does not have compressed suffix", g.inf); return; } @@ -3448,7 +3505,7 @@ local void process(char *path) /* open input file */ g.ind = open(g.inf, O_RDONLY, 0); if (g.ind < 0) - bail("read error on ", g.inf); + throw(errno, "read error on %s (%s)", g.inf, strerror(errno)); /* prepare gzip header information for compression */ g.name = g.headis & 1 ? justname(g.inf) : NULL; @@ -3469,23 +3526,31 @@ local void process(char *path) if (g.ind != 0) close(g.ind); if (method != -1) - complain(method < 0 ? "%s is not compressed -- skipping" : - "%s has unknown compression method -- skipping", + complain(method < 0 ? "skipping: %s is not compressed" : + "skipping: %s has unknown compression method", g.inf); return; } /* if requested, test input file (possibly a special list) */ if (g.decode == 2) { - if (method == 8) - infchk(); - else { - unlzw(); - if (g.list) { - g.in_tot -= 3; - show_info(method, 0, g.out_tot, 0); + try { + if (method == 8) + infchk(); + else { + unlzw(); + if (g.list) { + g.in_tot -= 3; + show_info(method, 0, g.out_tot, 0); + } } } + catch (err) { + if (err.code != EFTYPE) + punt(err); + complain("skipping: %s", err.why); + drop(err); + } RELEASE(g.hname); if (g.ind != 0) close(g.ind); @@ -3505,14 +3570,12 @@ local void process(char *path) /* create output file out, descriptor outd */ if (path == NULL || g.pipeout) { /* write to stdout */ - g.outf = MALLOC(strlen("") + 1); - if (g.outf == NULL) - bail("not enough memory", ""); + g.outf = alloc(NULL, strlen("") + 1); strcpy(g.outf, ""); g.outd = 1; if (!g.decode && !g.force && isatty(g.outd)) - bail("trying to write compressed data to a terminal", - " (use -f to force)"); + throw(EINVAL, "trying to write compressed data to a terminal" + " (use -f to force)"); } else { char *to = g.inf, *sufx = ""; @@ -3536,9 +3599,7 @@ local void process(char *path) sufx = g.sufx; /* create output file and open to write */ - g.outf = MALLOC(pre + len + strlen(sufx) + 1); - if (g.outf == NULL) - bail("not enough memory", ""); + g.outf = alloc(NULL, pre + len + strlen(sufx) + 1); memcpy(g.outf, g.inf, pre); memcpy(g.outf + pre, to, len); strcpy(g.outf + pre + len, sufx); @@ -3564,7 +3625,7 @@ local void process(char *path) /* if exists and no overwrite, report and go on to next */ if (g.outd < 0 && errno == EEXIST) { - complain("%s exists -- skipping", g.outf); + complain("skipping: %s exists", g.outf); RELEASE(g.outf); RELEASE(g.hname); if (g.ind != 0) @@ -3574,7 +3635,7 @@ local void process(char *path) /* if some other error, give up */ if (g.outd < 0) - bail("write error on ", g.outf); + throw(errno, "write error on %s (%s)", g.outf, strerror(errno)); } SET_BINARY_MODE(g.outd); RELEASE(g.hname); @@ -3583,12 +3644,20 @@ local void process(char *path) if (g.verbosity > 1) fprintf(stderr, "%s to %s ", g.inf, g.outf); if (g.decode) { - if (method == 8) - infchk(); - else if (method == 257) - unlzw(); - else - cat(); + try { + if (method == 8) + infchk(); + else if (method == 257) + unlzw(); + else + cat(); + } + catch (err) { + if (err.code != EFTYPE) + punt(err); + complain("skipping: %s", err.why); + drop(err); + } } #ifndef NOTHREAD else if (g.procs > 1) @@ -3606,7 +3675,7 @@ local void process(char *path) close(g.ind); if (g.outd != 1) { if (close(g.outd)) - bail("write error on ", g.outf); + throw(errno, "write error on %s (%s)", g.outf, strerror(errno)); g.outd = -1; /* now prevent deletion on interrupt */ if (g.ind != 0) { copymeta(g.inf, g.outf); @@ -3768,11 +3837,11 @@ local size_t num(char *arg) size_t val = 0; if (*str == 0) - bail("internal error: empty parameter", ""); + throw(EINVAL, "internal error: empty parameter"); do { if (*str < '0' || *str > '9' || (val && ((~(size_t)0) - (*str - '0')) / val < 10)) - bail("invalid numeric parameter: ", arg); + throw(EINVAL, "invalid numeric parameter: %s", arg); val = val * 10 + (*str - '0'); } while (*++str); return val; @@ -3787,7 +3856,7 @@ local int option(char *arg) /* if no argument or dash option, check status of get */ if (get && (arg == NULL || *arg == '-')) { bad[1] = "bpSIM"[get - 1]; - bail("missing parameter after ", bad); + throw(EINVAL, "missing parameter after %s", bad); } if (arg == NULL) return 0; @@ -3809,7 +3878,7 @@ local int option(char *arg) break; } if (j < 0) - bail("invalid option: ", arg - 2); + throw(EINVAL, "invalid option: %s", arg - 2); } /* process short options (more than one allowed after dash) */ @@ -3818,7 +3887,8 @@ local int option(char *arg) options until we have the parameter */ if (get) { if (get == 3) - bail("invalid usage: -s must be followed by space", ""); + throw(EINVAL, "invalid usage: " + "-s must be followed by space"); break; /* allow -pnnn and -bnnn, fall to parameter code */ } @@ -3830,11 +3900,11 @@ local int option(char *arg) g.level = *arg - '0'; while (arg[1] >= '0' && arg[1] <= '9') { if (g.level && (INT_MAX - (arg[1] - '0')) / g.level < 10) - bail("only levels 0..9 and 11 are allowed", ""); + throw(EINVAL, "only levels 0..9 and 11 are allowed"); g.level = g.level * 10 + *++arg - '0'; } if (g.level == 10 || g.level > 11) - bail("only levels 0..9 and 11 are allowed", ""); + throw(EINVAL, "only levels 0..9 and 11 are allowed"); new_opts(); break; case 'F': g.zopts.blocksplittinglast = 1; break; @@ -3857,9 +3927,11 @@ local int option(char *arg) case 'T': g.headis &= ~0xa; break; case 'V': fputs(VERSION, stderr); exit(0); case 'Z': - bail("invalid option: LZW output not supported: ", bad); + throw(EINVAL, "invalid option: LZW output not supported: %s", + bad); case 'a': - bail("invalid option: ascii conversion not supported: ", bad); + throw(EINVAL, "invalid option: no ascii conversion: %s", + bad); case 'b': get = 1; break; case 'c': g.pipeout = 1; break; case 'd': if (!g.decode) g.headis >>= 2; g.decode = 1; break; @@ -3876,7 +3948,7 @@ local int option(char *arg) case 'v': g.verbosity++; break; case 'z': g.form = 1; g.sufx = ".zz"; break; default: - bail("invalid option: ", bad); + throw(EINVAL, "invalid option: %s", bad); } } while (*++arg); if (*arg == 0) @@ -3891,24 +3963,24 @@ local int option(char *arg) n = num(arg); g.block = n << 10; /* chunk size */ if (g.block < DICT) - bail("block size too small (must be >= 32K)", ""); + throw(EINVAL, "block size too small (must be >= 32K)"); if (n != g.block >> 10 || OUTPOOL(g.block) < g.block || (ssize_t)OUTPOOL(g.block) < 0 || g.block > (1UL << 29)) /* limited by append_len() */ - bail("block size too large: ", arg); + throw(EINVAL, "block size too large: %s", arg); new_opts(); } else if (get == 2) { n = num(arg); g.procs = (int)n; /* # processes */ if (g.procs < 1) - bail("invalid number of processes: ", arg); + throw(EINVAL, "invalid number of processes: %s", arg); if ((size_t)g.procs != n || INBUFS(g.procs) < 1) - bail("too many processes: ", arg); + throw(EINVAL, "too many processes: %s", arg); #ifdef NOTHREAD if (g.procs > 1) - bail("compiled without threads", ""); + throw(EINVAL, "compiled without threads"); #endif new_opts(); } @@ -3926,130 +3998,140 @@ local int option(char *arg) return 1; } -/* catch termination signal */ -local void cut_short(int sig) +#ifndef NOTHREAD +/* handle error received from yarn function */ +local void cut_yarn(int err) { - (void)sig; - Trace(("termination by user")); - if (g.outd != -1 && g.outf != NULL) - unlink(g.outf); - log_dump(); - _exit(1); + throw(err, err == ENOMEM ? "not enough memory" : "internal threads error"); } +#endif -/* Process arguments, compress in the gzip format. Note that procs must be at - least two in order to provide a dictionary in one work unit for the other - work unit, and that size must be at least 32K to store a full dictionary. */ +/* Process command line arguments. */ int main(int argc, char **argv) { int n; /* general index */ int noop; /* true to suppress option decoding */ unsigned long done; /* number of named files processed */ char *opts, *p; /* environment default options, marker */ + ball_t err; /* error information from throw() */ + + try { + /* initialize globals */ + g.inf = NULL; + g.inz = 0; + g.outf = NULL; + g.first = 1; + g.hname = NULL; - /* initialize globals */ - g.inf = NULL; - g.inz = 0; - g.outf = NULL; - g.first = 1; - g.hname = NULL; - - /* save pointer to program name for error messages */ - p = strrchr(argv[0], '/'); - p = p == NULL ? argv[0] : p + 1; - g.prog = *p ? p : "pigz"; + /* save pointer to program name for error messages */ + p = strrchr(argv[0], '/'); + p = p == NULL ? argv[0] : p + 1; + g.prog = *p ? p : "pigz"; - /* prepare for interrupts and logging */ - signal(SIGINT, cut_short); + /* prepare for interrupts and logging */ + signal(SIGINT, cut_short); #ifndef NOTHREAD - yarn_prefix = g.prog; /* prefix for yarn error messages */ - yarn_abort = cut_short; /* call on thread error */ + yarn_prefix = g.prog; /* prefix for yarn error messages */ + yarn_abort = cut_yarn; /* call on thread error */ #endif #ifdef DEBUG - gettimeofday(&start, NULL); /* starting time for log entries */ - log_init(); /* initialize logging */ + gettimeofday(&start, NULL); /* starting time for log entries */ + log_init(); /* initialize logging */ #endif - /* set all options to defaults */ - defaults(); - - /* process user environment variable defaults in GZIP */ - opts = getenv("GZIP"); - if (opts != NULL) { - while (*opts) { - while (*opts == ' ' || *opts == '\t') - opts++; - p = opts; - while (*p && *p != ' ' && *p != '\t') - p++; - n = *p; - *p = 0; - if (option(opts)) - bail("cannot provide files in GZIP environment variable", ""); - opts = p + (n ? 1 : 0); + /* set all options to defaults */ + defaults(); + + /* process user environment variable defaults in GZIP */ + opts = getenv("GZIP"); + if (opts != NULL) { + while (*opts) { + while (*opts == ' ' || *opts == '\t') + opts++; + p = opts; + while (*p && *p != ' ' && *p != '\t') + p++; + n = *p; + *p = 0; + if (option(opts)) + throw(EINVAL, "cannot provide files in " + "GZIP environment variable"); + opts = p + (n ? 1 : 0); + } + option(NULL); } - option(NULL); - } - /* process user environment variable defaults in PIGZ as well */ - opts = getenv("PIGZ"); - if (opts != NULL) { - while (*opts) { - while (*opts == ' ' || *opts == '\t') - opts++; - p = opts; - while (*p && *p != ' ' && *p != '\t') - p++; - n = *p; - *p = 0; - if (option(opts)) - bail("cannot provide files in PIGZ environment variable", ""); - opts = p + (n ? 1 : 0); + /* process user environment variable defaults in PIGZ as well */ + opts = getenv("PIGZ"); + if (opts != NULL) { + while (*opts) { + while (*opts == ' ' || *opts == '\t') + opts++; + p = opts; + while (*p && *p != ' ' && *p != '\t') + p++; + n = *p; + *p = 0; + if (option(opts)) + throw(EINVAL, "cannot provide files in " + "PIGZ environment variable"); + opts = p + (n ? 1 : 0); + } + option(NULL); } - option(NULL); - } - /* decompress if named "unpigz" or "gunzip", to stdout if "*cat" */ - if (strcmp(g.prog, "unpigz") == 0 || strcmp(g.prog, "gunzip") == 0) { - if (!g.decode) - g.headis >>= 2; - g.decode = 1; - } - if ((n = strlen(g.prog)) > 2 && strcmp(g.prog + n - 3, "cat") == 0) { - if (!g.decode) - g.headis >>= 2; - g.decode = 1; - g.pipeout = 1; - } - - /* if no arguments and compressed data to or from a terminal, show help */ - if (argc < 2 && isatty(g.decode ? 0 : 1)) - help(); - - /* process command-line arguments, no options after "--" */ - done = noop = 0; - for (n = 1; n < argc; n++) - if (noop == 0 && strcmp(argv[n], "--") == 0) { - noop = 1; - option(NULL); + /* decompress if named "unpigz" or "gunzip", to stdout if "*cat" */ + if (strcmp(g.prog, "unpigz") == 0 || strcmp(g.prog, "gunzip") == 0) { + if (!g.decode) + g.headis >>= 2; + g.decode = 1; } - else if (noop || option(argv[n])) { /* true if file name, process it */ - if (done == 1 && g.pipeout && !g.decode && !g.list && g.form > 1) - complain("warning: output will be concatenated zip files -- " - "will not be able to extract"); - process(strcmp(argv[n], "-") ? argv[n] : NULL); - done++; + if ((n = strlen(g.prog)) > 2 && strcmp(g.prog + n - 3, "cat") == 0) { + if (!g.decode) + g.headis >>= 2; + g.decode = 1; + g.pipeout = 1; } - option(NULL); - /* list stdin or compress stdin to stdout if no file names provided */ - if (done == 0) - process(NULL); + /* if no arguments and compressed data to/from terminal, show help */ + if (argc < 2 && isatty(g.decode ? 0 : 1)) + help(); + + /* process command-line arguments */ + done = noop = 0; + for (n = 1; n < argc; n++) + /* ignore options after "--" */ + if (noop == 0 && strcmp(argv[n], "--") == 0) { + noop = 1; + option(NULL); + } + /* process argument, interpreting if option */ + else if (noop || option(argv[n])) { + /* argv[n] is a name to process */ + if (done == 1 && g.pipeout && !g.decode && !g.list && + g.form > 1) + complain("warning: output will be concatenated zip files" + " -- %s will not be able to extract", g.prog); + process(strcmp(argv[n], "-") ? argv[n] : NULL); + done++; + } + option(NULL); + + /* list stdin or compress stdin to stdout if no file names provided */ + if (done == 0) + process(NULL); + } + always { + /* release resources */ + RELEASE(g.inf); + g.inz = 0; + new_opts(); + } + catch (err) { + THREADABORT(err); + } - /* done -- release resources, show log */ - RELEASE(g.inf); - g.inz = 0; - new_opts(); + /* show log (if any) */ log_dump(); return 0; } diff --git a/try.c b/try.c new file mode 100644 index 0000000..5f78d08 --- /dev/null +++ b/try.c @@ -0,0 +1,74 @@ +/* try.c -- try / catch / throw exception handling for C99 + * Copyright (C) 2013, 2015 Mark Adler + * Version 1.2 19 January 2015 + * For conditions of distribution and use, see copyright notice in try.h + */ + +/* See try.h for documentation. This source file provides the global pointer + and the functions needed by throw(). The pointer is thread-unique if + pthread.h is included in try.h. */ + +#include "try.h" +#include +#include +#include + +/* Set up the try stack with a global pointer to the next try block. The + global is thread-unique if pthread.h is included in try.h. */ +#ifdef PTHREAD_ONCE_INIT + pthread_key_t try_key_; + static pthread_once_t try_once_ = PTHREAD_ONCE_INIT; + static void try_create_(void) + { + int ret = pthread_key_create(&try_key_, NULL); + assert(ret == 0 && "try: pthread_key_create() failed"); + } + void try_setup_(void) + { + int ret = pthread_once(&try_once_, try_create_); + assert(ret == 0 && "try: pthread_once() failed"); + } +#else /* !PTHREAD_ONCE_INIT */ + try_t_ *try_stack_ = NULL; +#endif /* PTHREAD_ONCE_INIT */ + +/* Throw an exception. This must always have at least two arguments, where the + second argument can be a NULL. The throw() macro is permitted to have one + argument, since it appends a NULL argument in the call to this function. */ +void try_throw_(int code, char *fmt, ...) +{ + /* save the thrown information in the try stack before jumping */ + try_setup_(); + assert(try_stack_ != NULL && "try: naked throw"); + try_stack_->ball.code = code; + try_stack_->ball.free = 0; + try_stack_->ball.why = fmt; + + /* consider the second argument to be a string, and if it has formatting + commands, process them with the subsequent arguments of throw, saving + the result in allocated memory -- this if statement and clause must be + updated for a different interpretation of the throw() arguments and + different contents of the ball_t structure */ + if (fmt != NULL && strchr(fmt, '%') != NULL) { + char *why, nul[1]; + size_t len; + va_list ap1, ap2; + + va_start(ap1, fmt); + va_copy(ap2, ap1); + len = vsnprintf(nul, 1, fmt, ap1); + va_end(ap1); + why = malloc(len + 1); + if (why == NULL) + try_stack_->ball.why = "try: out of memory"; + else { + vsnprintf(why, len + 1, fmt, ap2); + va_end(ap2); + try_stack_->ball.free = 1; + try_stack_->ball.why = why; + } + } + + /* jump to the end of the nearest enclosing try block */ + longjmp(try_stack_->env, 2); +} diff --git a/try.h b/try.h new file mode 100644 index 0000000..03289dd --- /dev/null +++ b/try.h @@ -0,0 +1,465 @@ +/* try.h -- try / catch / throw exception handling for C99 + Copyright (C) 2013, 2015 Mark Adler + Version 1.2 19 January 2015 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* + Version History + 1.0 7 Jan 2013 - First version + 1.1 2 Nov 2013 - Use variadic macros and functions instead of partial + structure assignment, allowing arbitrary arguments + to printf() + 1.2 19 Jan 2015 - Obey setjmp() invocation limits from C standard + */ + +/* To use, include try.h in all source files that use these operations, and + compile and link try.c. If pthread threads are used, then there must be an + #include in try.h to make the exception handling thread-safe. + (Uncomment the include below.) If threads other than pthread are being + used, then try.h and try.c must be modified to use that environment's + thread-local storage for the try_stack_ pointer. try.h and try.c assume + that the compiler and library conform to the C99 standard, at least with + respect to the use of variadic macro and function arguments. */ + +/* + try.h provides a try / catch / throw exception handler, which allows + catching exceptions across any number of levels of function calls. try + blocks can be nested as desired, with a throw going to the end of the + innermost enclosing try, passing the thrown information to the associated + catch block. A global try stack is used, to avoid having to pass exception + handler information through all of the functions down to the invocations of + throw. The try stack is thread-unique if requested by uncommenting the + pthread.h include below. In addition to the macros try, catch, and throw, + the macros always, retry, punt, and drop, and the type ball_t are created. + All other symbols are of the form try_*_ or TRY_*_, where the final + underscore should avoid conflicts with application symbols. The eight + exposed names can be changed easily in #defines below. + + A try block encloses code that may throw an exception with the throw() + macro, either directly in the try block or in any function called directly + or indirectly from the try block. throw() must have at least one argument, + which is an integer. The try block is followed by a catch block whose code + will be executed when throw() is called with a non-zero first argument. If + the first argument of throw() is zero, then execution continues after the + catch block. If the try block completes normally, with no throw() being + called, then execution continues normally after the catch block. + + There can be only one catch block. catch has one argument which must be a + ball_t type variable declared in the current function or block containing + the try and catch. That variable is loaded with the information sent by the + throw() for use in the catch block. + + throw() may optionally include more information that is passed to the catch + block in the ball_t structure. throw() can have one or more arguments, + where the first (possibly only) argument is an integer code. The second + argument can be a pointer, which will be replaced by NULL in the ball_t + structure if not provided. The implementation of throw() in try.c assumes + that if the second argument is present and is not NULL, that it is a string. + If that string has any percent (%) signs in it, then throw() will run that + string through vsnprintf() with any other arguments provided after the + string in the throw() invocation, and save the resulting formatted string in + the ball_t structure. Information on whether or not the string was + allocated is also maintained in the ball_t structure. + + throw() in try.c can be modified to not assume that the second argument is a + string. For example, an application may want to assume instead that the + second argument is a pointer to a set of information for use in the catch + block. + + The catch block may conditionally do a punt(), where the argument of punt() + is the argument of catch. This passes the exception on to the next + enclosing try/catch handler. + + If a catch block does not always end with a punt(), it should contain a + drop(), where the argument of drop() is the argument of catch. This frees + the allocated string made if vsnprintf() was used by throw() to generate the + string. If printf() format strings are never used, then drop() is not + required. + + An always block may be placed between the try and catch block. The + statements in that block will be executed regardless of whether or not the + try block completed normally. As indicated by the ordering, the always + block will be executed before the catch block. This block is not named + "finally", since it is different from the finally block in other languages + which is executed after the catch block. + + A naked break or continue in a try or always block will go directly to the + end of that block. + + A retry from the try block or from any function called from the try block at + any level of nesting will restart the try block from the beginning. + + try is thread-safe when compiled with pthread.h. A throw() in a thread can + only be caught in the same thread. If a throw() is attempted from a thread + without an enclosing try in that thread, even if in another thread there is + a try around the pthread_create() that spawned this thread, then the throw + will fail on an assert. Each thread has its own thread-unique try stack, + which starts off empty. + + If an intermediate function does not have a need for operations in a catch + block other than punt, and does not need an always block, then that function + does not need a try block. "try { block } catch (err) { punt(err); }" is + the same as just "block". More precisely, it's equivalent to "do { block } + while (0);", which replicates the behavior of a naked break or continue in a + block when it follows try. throw() can be used from a function that has no + try. All that is necessary is that there is a try somewhere up the function + chain that called the current function in the current thread. + + There must not be a return in any try block, nor a goto in any try block + that leaves that block. The always block does not catch a return from the + try block. There is no check or protection for an improper use of return or + goto. It is up to the user to assure that this doesn't happen. If it does + happen, then the reference to the current try block is left on the try + stack, and the next throw which is supposed to go to an enclosing try would + instead go to this try, possibly after the enclosing function has returned. + Mayhem will then ensue. This may be caught by the longjmp() implementation, + which would report "longjmp botch" and then abort. + + Any automatic storage variables that are modified in the try block and used + in the catch or always block must be declared volatile. Otherwise their + value in the catch or always block is indeterminate. + + Any statements between try and always, between try and catch if there is no + always, or between always and catch are part of those respective try or + always blocks. Use of { } to enclose those blocks is optional, but { } + should be used anyway for clarity, style, and to inform smart source editors + that the enclosed code is to be indented. Enclosing the catch block with { + } is not optional if there is more than one statement in the block. + However, even if there is just one statement in the catch block, it should + be enclosed in { } anyway for style and editing convenience. + + The contents of the ball_t structure after the first element (int code) can + be customized for the application. If ball_t is customized, then the code + in try.c should be updated accordingly. If there is no memory allocation in + throw(), then drop() can be eliminated. + + Example usage: + + ball_t err; + volatile char *temp = NULL; + try { + ... do something ... + if (ret == -1) + throw(1, "bad thing happened to %s\n", me); + temp = malloc(sizeof(me) + 1); + if (temp == NULL) + throw(2, "out of memory"); + ... do more ... + if (ret == -1) + throw(3, "worse thing happened to %s\n", temp); + ... some more code ... + } + always { + free(temp); + } + catch (err) { + fputs(err.why, stderr); + drop(err); + return err.code; + } + ... end up here if nothing bad happened ... + + + More involved example: + + void check_part(void) + { + ball_t err; + + try { + ... + if (part == bad1) + throw(1); + ... + if (part == bad2) + throw(1); + ... + } + catch (err) { + drop(err); + throw(3, "part was bad"); + } + } + + void check_input(void) + { + ... + if (input == wrong) + throw(4, "input was wrong"); + ... + if (input == stupid) + throw(5, "input was stupid"); + ... + check_part(); + ... + } + + void *build_something(void) + { + ball_t err; + volatile void *thing; + try { + thing = malloc(sizeof(struct thing)); + ... build up thing ... + check_input(); + ... finish building it ... + } + catch (err) { + free(thing); + punt(err); + } + return thing; + } + + int grand_central(void) + { + ball_t err; + void *thing; + try { + thing = build_something(); + } + catch (err) { + fputs(err.why, stderr); + drop(err); + return err.code; + } + ... use thing ... + free(thing); + return 0; + } + + */ + +#ifndef _TRY_H +#define _TRY_H + +#include +#include +#include +#include + +/* If pthreads are used, uncomment this include to make try thread-safe. */ +#ifndef NOTHREAD +# include +#endif + +/* The exposed names can be changed here. */ +#define ball_t try_ball_t_ +#define try TRY_TRY_ +#define always TRY_ALWAYS_ +#define catch TRY_CATCH_ +#define throw TRY_THROW_ +#define retry TRY_RETRY_ +#define punt TRY_PUNT_ +#define drop TRY_DROP_ + +/* Package of an integer code and any other data to be thrown and caught. Here, + why is a string with information to be displayed to indicate why an + exception was thrown. free is true if why was allocated and should be freed + when no longer needed. This structure can be customized as needed, but it + must start with an int code. If it is customized, the try_throw_() function + in try.c must also be updated accordingly. As an example, why could be a + structure with information for use in the catch block. */ +typedef struct { + int code; /* integer code (required) */ + int free; /* if true, the message string was allocated */ + char *why; /* informational string or NULL */ +} try_ball_t_; + +/* Element in the global try stack (a linked list). */ +typedef struct try_s_ try_t_; +struct try_s_ { + jmp_buf env; /* state information for longjmp() to jump back */ + try_ball_t_ ball; /* data passed from the throw() */ + try_t_ *next; /* link to the next enclosing try_t, or NULL */ +}; + +/* Global try stack. try.c must be compiled and linked to provide the stack + pointer. Use thread-local storage if pthread.h is included before this. + Note that a throw can only be caught within the same thread. A new and + unique try stack is created for each thread, so any attempt to throw across + threads will fail with an assert, by virtue of reaching the end of the + stack. */ +#ifdef PTHREAD_ONCE_INIT + extern pthread_key_t try_key_; + void try_setup_(void); +# define try_stack_ ((try_t_ *)pthread_getspecific(try_key_)) +# define try_stack_set_(next) \ + do { \ + int try_ret_ = pthread_setspecific(try_key_, next); \ + assert(try_ret_ == 0 && "try: pthread_setspecific() failed"); \ + } while (0) +#else /* !PTHREAD_ONCE_INIT */ + extern try_t_ *try_stack_; +# define try_setup_() +# define try_stack_set_(next) try_stack_ = (next) +#endif /* PTHREAD_ONCE_INIT */ + +/* Try a block. The block should follow the invocation of try enclosed in { }. + The block must be immediately followed by an always or a catch. You must + not goto or return out of the try block. A naked break or continue in the + try block will go to the end of the block. */ +#define TRY_TRY_ \ + do { \ + try_t_ try_this_; \ + int try_pushed_ = 1; \ + try_this_.ball.code = 0; \ + try_this_.ball.free = 0; \ + try_this_.ball.why = NULL; \ + try_setup_(); \ + try_this_.next = try_stack_; \ + try_stack_set_(&try_this_); \ + if (setjmp(try_this_.env) < 2) \ + do { \ + +/* Execute the code between always and catch, whether or not something was + thrown. An always block is optional. If present, the always block must + follow a try block and be followed by a catch block. The always block + should be enclosed in { }. A naked break or continue in the always block + will go to the end of the block. It is permitted to use throw in the always + block, which will fall up to the next enclosing try. However this will + result in a memory leak if the original throw() allocated space for the + informational string. So it's best to not throw() in an always block. Keep + the always block simple. + + Great care must be taken if the always block uses an automatic storage + variable local to the enclosing function that can be modified in the try + block. Such variables must be declared volatile. If such a variable is not + declared volatile, and if the compiler elects to keep that variable in a + register, then the throw will restore that variable to its state at the + beginning of the try block, wiping out any change that occurred in the try + block. This can cause very confusing bugs until you remember that you + didn't follow this rule. */ +#define TRY_ALWAYS_ \ + } while (0); \ + if (try_pushed_) { \ + try_stack_set_(try_this_.next); \ + try_pushed_ = 0; \ + } \ + do { + +/* Catch an error thrown in the preceding try block. The catch block must + follow catch and its parameter, and must be enclosed in { }. The catch must + immediately follow the try or always block. It is permitted to use throw() + in the catch block, which will fall up to the next enclosing try. However + the ball_t passed by throw() must be freed using drop() before doing another + throw, to avoid a potential memory leak. The parameter of catch must be a + ball_t declared in the function or block containing the catch. It is set to + the parameters of the throw() that jumped to the catch. The catch block is + not executed if the first parameter of the throw() was zero. + + A catch block should end with either a punt() or a drop(). + + Great care must be taken if the catch block uses an automatic storage + variable local to the enclosing function that can be modified in the try + block. Such variables must be declared volatile. If such a variable is not + declared volatile, and if the compiler elects to keep that variable in a + register, then the throw will restore that variable to its state at the + beginning of the try block, wiping out any change that occurred in the try + block. This can cause very confusing bugs until you remember that you + didn't follow this rule. */ +#define TRY_CATCH_(try_ball_) \ + } while (0); \ + if (try_pushed_) { \ + try_stack_set_(try_this_.next); \ + try_pushed_ = 0; \ + } \ + try_ball_ = try_this_.ball; \ + } while (0); \ + if (try_ball_.code) + +/* Throw an error. This can be in the try block or in any function called from + the try block, at any level of nesting. This will fall back to the end of + the first enclosing try block in the same thread, invoking the associated + catch block with a ball_t set to the arguments of throw(). throw() will + abort the program with an assert() if there is no nesting try. Make sure + that there's a nesting try! + + try may have one or more arguments, where the first argument is an int, the + optional second argument is a string, and the remaining optional arguments + are referred to by printf() formatting commands in the string. If there are + formatting commands in the string, i.e. any percent (%) signs, then + vsnprintf() is used to generate the formatted string from the arguments + before jumping to the enclosing try block. This allows throw() to use + information on the stack in the scope of the throw() statement, which will + be lost after jumping back to the enclosing try block. That formatted + string will use allocated memory, which is why it is important to use drop() + in catch blocks to free that memory, or punt() to pass the string on to + another catch block. Eventually some catch block down the chain will have + to drop() it. + + If a memory allocation fails during the execution of a throw(), then the + string provided to the catch block is not the formatted string at all, but + rather the string: "try: out of memory", with the integer code from the + throw() unchanged. + + If the first argument of throw is zero, then the catch block is not + executed. A throw(0) from a function called in the try block is equivalent + to a break or continue in the try block. A throw(0) should not have any + other arguments, to avoid a potential memory leak. There is no opportunity + to make use of any arguments after the 0 anyway. + + try.c must be compiled and linked to provide the try_throw_() function. */ +void try_throw_(int code, char *fmt, ...); +#define TRY_THROW_(...) try_throw_(__VA_ARGS__, NULL) + +/* Retry the try block. This will start over at the beginning of the try + block. This can be used in the try block or in any function called from the + try block at any level of nesting, just like throw. retry has no argument. + If there is a retry in the always or catch block, then it will retry the + next enclosing try, not the immediately preceding try. + + If you use this, make sure you have carefully thought through how it will + work. It can be tricky to correctly rerun a chunk of code that has been + partially executed. Especially if there are different degrees of progress + that could have been made. Also note that automatic variables changed in + the try block and not declared volatile will have indeterminate values. + + We use 1 here instead of 0, since some implementations prevent returning a + zero value from longjmp() to setjmp(). */ +#define TRY_RETRY_ \ + do { \ + try_setup_(); \ + assert(try_stack_ != NULL && "try: naked retry"); \ + longjmp(try_stack_->env, 1); \ + } while (0) + +/* Punt a caught error on to the next enclosing catcher. This is normally used + in a catch block with same argument as the catch. */ +#define TRY_PUNT_(try_ball_) \ + do { \ + try_setup_(); \ + assert(try_stack_ != NULL && "try: naked punt"); \ + try_stack_->ball = try_ball_; \ + longjmp(try_stack_->env, 2); \ + } while (0) + +/* Clean up at the end of the line in a catch (no more punts). */ +#define TRY_DROP_(try_ball_) \ + do { \ + if (try_ball_.free) { \ + free(try_ball_.why); \ + try_ball_.free = 0; \ + try_ball_.why = NULL; \ + } \ + } while (0) + +#endif /* _TRY_H */ diff --git a/yarn.c b/yarn.c index 5e557f3..459805d 100644 --- a/yarn.c +++ b/yarn.c @@ -1,6 +1,6 @@ /* yarn.c -- generic thread operations implemented using pthread functions - * Copyright (C) 2008, 2012 Mark Adler - * Version 1.3 13 Jan 2012 Mark Adler + * Copyright (C) 2008, 2011, 2012, 2015 Mark Adler + * Version 1.4 19 Jan 2015 Mark Adler * For conditions of distribution and use, see copyright notice in yarn.h */ @@ -17,6 +17,8 @@ 1.3 13 Jan 2012 Add large file #define for consistency with pigz.c Update thread portability #defines per IEEE 1003.1-2008 Fix documentation in yarn.h for yarn_prefix + 1.4 19 Jan 2015 Allow yarn_abort() to avoid error message to stderr + Accept and do nothing for NULL argument to free_lock() */ /* for thread portability */ @@ -54,10 +56,10 @@ void (*yarn_abort)(int) = NULL; /* immediately exit -- use for errors that shouldn't ever happen */ local void fail(int err) { - fprintf(stderr, "%s: %s (%d) -- aborting\n", yarn_prefix, - err == ENOMEM ? "out of memory" : "internal pthread error", err); if (yarn_abort != NULL) yarn_abort(err); + fprintf(stderr, "%s: %s (%d) -- aborting\n", yarn_prefix, + err == ENOMEM ? "out of memory" : "internal pthread error", err); exit(err == ENOMEM || err == EAGAIN ? err : EINVAL); } @@ -172,6 +174,9 @@ long peek_lock(lock *bolt) void free_lock(lock *bolt) { int ret; + + if (bolt == NULL) + return; if ((ret = pthread_cond_destroy(&(bolt->cond))) || (ret = pthread_mutex_destroy(&(bolt->mutex)))) fail(ret); diff --git a/yarn.h b/yarn.h index 7e3f914..67cf56d 100644 --- a/yarn.h +++ b/yarn.h @@ -1,6 +1,6 @@ /* yarn.h -- generic interface for thread operations - * Copyright (C) 2008, 2011 Mark Adler - * Version 1.3 13 Jan 2012 Mark Adler + * Copyright (C) 2008, 2011, 2012, 2015 Mark Adler + * Version 1.4 19 Jan 2015 Mark Adler */ /*