From ee33470f83c7d7dc55109031f27bf1c3b74413c2 Mon Sep 17 00:00:00 2001 From: mdw Date: Fri, 10 Dec 1999 23:29:13 +0000 Subject: [PATCH] Emit random numbers for statistical tests. --- rspit.c | 946 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 946 insertions(+) create mode 100644 rspit.c diff --git a/rspit.c b/rspit.c new file mode 100644 index 0000000..2da0089 --- /dev/null +++ b/rspit.c @@ -0,0 +1,946 @@ +/* -*-c-*- + * + * $Id: rspit.c,v 1.1 1999/12/10 23:29:13 mdw Exp $ + * + * Spit out random numbers + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Catacomb. + * + * Catacomb is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Catacomb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Catacomb; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: rspit.c,v $ + * Revision 1.1 1999/12/10 23:29:13 mdw + * Emit random numbers for statistical tests. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifndef PORTABLE +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "grand.h" + +#include "lcrand.h" +#include "fibrand.h" +#include "rand.h" +#include "noise.h" + +#include "bbs.h" +#include "mprand.h" + +#include "rc4.h" + +#include "des-ofb.h" +#include "des3-ofb.h" +#include "rc5-ofb.h" +#include "blowfish-ofb.h" +#include "idea-ofb.h" + +#include "rmd160.h" + +/*----- Data structures ---------------------------------------------------*/ + +typedef struct gen { + const char *name; + grand *(*seed)(unsigned /*i*/); + unsigned i; + const char *help; +} gen; + +static gen generators[]; + +/*----- Miscellaneous static data -----------------------------------------*/ + +static FILE *outfp = stdout; +static size_t outsz = 0; + +static int argc; +static char **argv; + +static unsigned flags = 0; + +enum { + f_progress = 1, + f_file = 2 +}; + +/*----- Help options ------------------------------------------------------*/ + +static void usage(FILE *fp) +{ + pquis(fp, "Usage: $ generator [options]\n"); +} + +static void version(FILE *fp) +{ + pquis(fp, "$, Catacomb version " VERSION "\n"); +} + +static void help(FILE *fp) +{ + version(fp); + fputc('\n', fp); + usage(fp); + pquis(fp, "\n\ +Emits a stream of random bytes suitable for, well, all sorts of things.\n\ +The primary objective is to be able to generate streams of input for\n\ +statistical tests, such as Diehard.\n\ +\n\ +Options are specific to the particular generator, although there's a\n\ +common core set:\n\ +\n\ +-h, --help Display this help message.\n\ +-v, --version Display the program's version number.\n\ +-u, --usage Display a useless usage message.\n\ +\n\ +-l, --list Show a list of the supported generators, with\n\ + their options.\n\ +-o, --output FILE Write output to FILE, not stdout.\n\ +-z, --size SIZE Emit SIZE bytes, not an unlimited number.\n\ +-p, --progress Show a little progress meter (on stderr).\n\ +\n\ +(A SIZE may be followed by `g' for gigabytes, `m' for megabytes, or\n\ +`k' for kilobytes. If unqualified, an amount in bytes is assumed.)\n\ +"); +} + +/*----- Main options parser -----------------------------------------------*/ + +static struct option opts[] = { + + /* --- Standard GNU help options --- */ + + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + + /* --- Other useful things --- */ + + { "list", 0, 0, 'l' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { "size", OPTF_ARGREQ, 0, 'z' }, + { "progress", 0, 0, 'p' }, + + /* --- End of main table --- */ + + { 0, 0, 0, 0 } +}; + +static const char *sopts = "hvu lo:z:p"; + +#ifndef OPTION_V + DA_DECL(option_v, struct option); +# define OPTION_V +#endif + +static option_v optv = DA_INIT; +static dstr optd = DSTR_INIT; + +/* --- @addopts@ --- * + * + * Arguments: @const char *s@ = pointer to short options + * @struct option *l@ = pointer to long options + * + * Returns: --- + * + * Use: Adds a collection of options to the table. + */ + +static void addopts(const char *s, struct option *l) +{ + dstr_puts(&optd, s); + if (DA_LEN(&optv)) + DA_SHRINK(&optv, 1); + while (l->name) + DA_PUSH(&optv, *l++); + DA_PUSH(&optv, *l); +} + +/* --- @opt@ --- * + * + * Arguments: --- + * + * Returns: Next option from argument array. + * + * Use: Fetches options, handling the standard ones. + */ + +static int opt(void) +{ + for (;;) { + int i = mdwopt(argc, argv, optd.buf, DA(&optv), 0, 0, 0); + switch (i) { + case 'h': + help(stdout); + exit(0); + case 'v': + version(stdout); + exit(0); + case 'u': + usage(stdout); + exit(0); + case 'l': { + gen *g; + puts("Generators supported:"); + for (g = generators; g->name; g++) + printf(" %s %s\n", g->name, g->help); + exit(0); + } break; + case 'o': + if (flags & f_file) + die(EXIT_FAILURE, "already set an output file"); + if (strcmp(optarg, "-") == 0) + outfp = stdout; + else { + outfp = fopen(optarg, "w"); + if (!outfp) { + die(EXIT_FAILURE, "couldn't open output file `%s': %s", + strerror(errno)); + } + } + flags |= f_file; + break; + case 'z': { + char *p; + outsz = strtoul(optarg, &p, 0); + if (!outsz) + die(EXIT_FAILURE, "bad number `%s'", optarg); + switch (*p) { + case 'G': case 'g': outsz *= 1024; + case 'M': case 'm': outsz *= 1024; + case 'K': case 'k': outsz *= 1024; + case 0: + break; + default: + die(EXIT_FAILURE, "bad suffix `%s'", p); + break; + } + if (*p && p[1] != 0) + die(EXIT_FAILURE, "bad suffix `%s'", p); + } break; + case 'p': + flags |= f_progress; + break; + default: + return (i); + } + } +} + +/*----- Manglers for seed strings -----------------------------------------*/ + +/* --- @unhex@ --- * + * + * Arguments: @const char *p@ = pointer to input string + * @char **end@ = where the end goes + * @dstr *d@ = output buffer + * + * Returns: --- + * + * Use: Transforms a hex string into a chunk of binary data. + */ + +static void unhex(const char *p, char **end, dstr *d) +{ + while (p[0] && p[1]) { + int x = p[0], y = p[1]; + if ('0' <= x && x <= '9') x -= '0'; + else if ('A' <= x && x <= 'F') x -= 'A' - 10; + else if ('a' <= x && x <= 'f') x -= 'a' - 10; + else x = 0; + if ('0' <= y && y <= '9') y -= '0'; + else if ('A' <= y && y <= 'F') y -= 'A' - 10; + else if ('a' <= y && y <= 'f') y -= 'a' - 10; + else y = 0; + DPUTC(d, (x << 4) + y); + p += 2; + } + *end = (char *)p; +} + +/*----- Generators --------------------------------------------------------*/ + +/* --- Blum-Blum-Shub strong generator --- */ + +static int bbsev(int ev, mp *m, void *p) +{ + switch (ev) { + case BBSEV_FINDP: + fputs("Searching for p: ", stderr); + fflush(stderr); + break; + case BBSEV_FINDQ: + fputs("Searching for q: ", stderr); + fflush(stderr); + break; + case BBSEV_FAILP: + case BBSEV_FAILQ: + fputc('.', stderr); + fflush(stderr); + break; + case BBSEV_PASSP: + case BBSEV_PASSQ: + fputc('+', stderr); + fflush(stderr); + break; + case BBSEV_GOODP: + case BBSEV_GOODQ: + fputc('\n', stderr); + fflush(stderr); + break; + } + return (0); +} + +static grand *gen_bbs(unsigned i) +{ + /* --- Default modulus --- * + * + * The factors of this number are + * + * @p = 1229936431484295969649886203367009966370895964206162032259292413@ + * @7754313537966036459299022912838407755462506416274551744201653277@ + * @313130311731673973886822067@ + * + * @q = 9798171783943489959487301695884963889684294764514008432498259742@ + * @5374320073594018817245784145742769603334292182227671519041431067@ + * @61344781426317516045890159@ + * + * Both %$p$% and %$q$% are prime; %$(p - 1)/2%$ and %$(q - 1)/2$% have no + * common factors. They were found using this program, with random + * starting points. + * + * I hope that, by publishing these factors, I'll dissuade people from + * actually using this modulus in attempt to actually attain real + * security. The program is quite quick at finding Blum numbers, so + * there's no excuse for not generating your own. + */ + + const char *mt = + "120511284390135742513572142094334711443073194119732569353820828435640527418092392240366088035509890969913081816369160298961490135716255689660470370755013177656905237112577648090277537209936078171554274553448103698084782669252936352843649980105109850503830397166360721262431179505917248447259735253684659338653"; + + /* --- Other things --- */ + + grand *r; + const char *xt = 0; + unsigned bits = 512; + mp *m, *x; + unsigned show = 0; + + /* --- Parse options --- */ + + static struct option opts[] = { + { "modulus", OPTF_ARGREQ, 0, 'm' }, + { "generate", 0, 0, 'g' }, + { "seed", OPTF_ARGREQ, 0, 's' }, + { "bits", OPTF_ARGREQ, 0, 'b' }, + { "show", 0, 0, 'S' }, + { 0, 0, 0, 0 } + }; + + addopts("m:gs:b:S", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 'm': + mt = optarg; + break; + case 'g': + mt = 0; + break; + case 's': + xt = optarg; + break; + case 'b': + bits = strtoul(optarg, 0, 0); + if (bits == 0) + die(EXIT_FAILURE, "bad number of bits `%s'", optarg); + break; + case 'S': + show = 1; + break; + default: + return (0); + } + } + + /* --- Generate a modulus if one is requested --- */ + + if (mt) { + char *p; + m = mp_readstring(MP_NEW, mt, &p, 0); + if (*p) + die(EXIT_FAILURE, "bad modulus `%s'", mt); + /* Unfortunately I don't know how to test for a Blum integer */ + } else { + mp *p = mprand(MP_NEW, bits / 2, &rand_global, 3); + mp *q = mprand(MP_NEW, bits - bits / 2, &rand_global, 3); + bbs_params bp; + int err; + + if ((err = bbs_gen(&bp, p, q, 0, + (flags & f_progress) ? bbsev : 0, 0)) != 0) + die(EXIT_FAILURE, "modulus generation failed (reason = %i)", err); + m = bp.n; + + if (show) { + fputs("p = ", stderr); + mp_writefile(bp.p, stderr, 10); + fputs("\nq = ", stderr); + mp_writefile(bp.q, stderr, 10); + fputs("\nn = ", stderr); + mp_writefile(bp.n, stderr, 10); + fputc('\n', stderr); + } + + mp_drop(p); + mp_drop(q); + mp_drop(bp.p); + mp_drop(bp.q); + } + + /* --- Set up a seed --- */ + + if (!xt) + x = mprand(MP_NEW, mp_bits(m) - 1, &rand_global, 1); + else { + char *p; + x = mp_readstring(MP_NEW, xt, &p, 0); + if (*p) + die(EXIT_FAILURE, "bad modulus `%s'", xt); + } + + /* --- Right --- */ + + r = bbs_rand(m, x); + + mp_drop(m); + mp_drop(x); + return (r); +} + +/* --- Catacomb's random number generator --- */ + +static grand *gen_rand(unsigned i) +{ + grand *r = rand_create(); + dstr d = DSTR_INIT; + + static struct option opts[] = { + { "key", OPTF_ARGREQ, 0, 'k' }, + { "text", OPTF_ARGREQ, 0, 't' }, + { "hex", OPTF_ARGREQ, 0, 'H' }, + { "noise", 0, 0, 'n' }, + { 0, 0, 0, 0 } + }; + + addopts("k:t:H:n", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 'k': { + rmd160_ctx c; + octet hash[RMD160_HASHSZ]; + rmd160_init(&c); + rmd160_hash(&c, optarg, strlen(optarg)); + rmd160_done(&c, hash); + r->ops->misc(r, RAND_KEY, hash, sizeof(hash)); + } break; + case 't': + r->ops->misc(r, GRAND_SEEDBLOCK, optarg, strlen(optarg)); + break; + case 'H': { + char *p; + DRESET(&d); + unhex(optarg, &p, &d); + if (*p) + die(EXIT_FAILURE, "bad hex key `%s'", optarg); + r->ops->misc(r, GRAND_SEEDBLOCK, d.buf, d.len); + } break; + case 'n': + r->ops->misc(r, RAND_NOISESRC, &noise_source); + break; + } + } + + dstr_destroy(&d); + return (r); +} + +/* --- RC4 output --- */ + +static grand *gen_rc4(unsigned i) +{ + grand *r; + dstr d = DSTR_INIT; + + static struct option opts[] = { + { "key", OPTF_ARGREQ, 0, 'k' }, + { "hex", OPTF_ARGREQ, 0, 'H' }, + { 0, 0, 0, 0 } + }; + + addopts("k:H:", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 'k': { + rmd160_ctx c; + dstr_ensure(&d, RMD160_HASHSZ); + rmd160_init(&c); + rmd160_hash(&c, optarg, strlen(optarg)); + rmd160_done(&c, d.buf); + d.len += RMD160_HASHSZ; + } break; + case 'H': { + char *p; + unhex(optarg, &p, &d); + if (*p) + die(EXIT_FAILURE, "bad hex key `%s'", optarg); + } break; + default: + return (0); + } + } + + if (!d.len) { + dstr_ensure(&d, 16); + d.len = 16; + rand_getgood(RAND_GLOBAL, d.buf, d.len); + } + r = rc4_rand(d.buf, d.len); + dstr_destroy(&d); + return (r); +} + +/* --- Output feedback generators --- */ + +#define OFBTAB \ + E(OFB_DES, DES_KEYSZ, DES_BLKSZ, des_ofbrand), \ + E(OFB_DES3, DES3_KEYSZ, DES3_BLKSZ, des3_ofbrand), \ + E(OFB_RC5, RC5_KEYSZ, RC5_BLKSZ, rc5_ofbrand), \ + E(OFB_BLOWFISH, BLOWFISH_KEYSZ, BLOWFISH_BLKSZ, blowfish_ofbrand), \ + E(OFB_IDEA, IDEA_KEYSZ, IDEA_BLKSZ, idea_ofbrand) + +static struct { + size_t keysz; + size_t blksz; + grand *(*rand)(const void */*k*/, size_t /*sz*/); +} ofbtab[] = { +#define E(c, x, y, z) { x, y, z } + OFBTAB +#undef E +}; + +enum { +#define E(c, x, y, z) c + OFBTAB +#undef E +}; + +#undef OFBTAB + +static grand *gen_ofb(unsigned i) +{ + grand *r; + dstr d = DSTR_INIT; + dstr iv = DSTR_INIT; + + static struct option opts[] = { + { "key", OPTF_ARGREQ, 0, 'k' }, + { "hex", OPTF_ARGREQ, 0, 'H' }, + { "iv", OPTF_ARGREQ, 0, 'i' }, + { 0, 0, 0, 0 } + }; + + addopts("k:H:i:", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 'k': { + rmd160_ctx c; + dstr_ensure(&d, RMD160_HASHSZ); + rmd160_init(&c); + rmd160_hash(&c, optarg, strlen(optarg)); + rmd160_done(&c, d.buf); + d.len += RMD160_HASHSZ; + } break; + case 'H': { + char *p; + unhex(optarg, &p, &d); + if (*p) + die(EXIT_FAILURE, "bad hex key `%s'", optarg); + } break; + case 'i': { + char *p; + unhex(optarg, &p, &iv); + if (*p) + die(EXIT_FAILURE, "bad hex IV `%s'", optarg); + } break; + default: + return (0); + } + } + + if (!d.len) { + size_t n = ofbtab[i].keysz; + if (!n) + n = 16; + dstr_ensure(&d, n); + d.len = n; + rand_getgood(RAND_GLOBAL, d.buf, d.len); + } + + while (d.len < ofbtab[i].keysz) + DPUTD(&d, &d); + if (ofbtab[i].keysz && d.len > ofbtab[i].keysz) + d.len = ofbtab[i].keysz; + + r = ofbtab[i].rand(d.buf, d.len); + if (iv.len) { + while (iv.len < ofbtab[i].blksz) + DPUTD(&iv, &iv); + r->ops->misc(r, GRAND_SEEDBLOCK, iv.buf); + } + + dstr_destroy(&d); + dstr_destroy(&iv); + return (r); +} + +/* --- Fibonacci generator --- */ + +static grand *gen_fib(unsigned i) +{ + grand *r; + uint32 s = 0; + char *p; + unsigned set = 0; + + static struct option opts[] = { + { "seed", OPTF_ARGREQ, 0, 's' }, + { 0, 0, 0, 0 } + }; + + addopts("s:", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 's': + s = strtoul(optarg, &p, 0); + if (*p) + die(EXIT_FAILURE, "bad integer `%s'", optarg); + set = 1; + break; + default: + return (0); + } + } + r = fibrand_create(s); + if (!set) + r->ops->misc(r, GRAND_SEEDRAND, &rand_global); + return (r); +} + +/* --- LC generator --- */ + +static grand *gen_lc(unsigned i) +{ + uint32 s = 0; + char *p; + unsigned set = 0; + + static struct option opts[] = { + { "seed", OPTF_ARGREQ, 0, 's' }, + { 0, 0, 0, 0 } + }; + + addopts("s:", opts); + + for (;;) { + int o = opt(); + if (o < 0) + break; + switch (o) { + case 's': + s = strtoul(optarg, &p, 0); + if (*p) + die(EXIT_FAILURE, "bad integer `%s'", optarg); + set = 1; + break; + default: + return (0); + } + } + if (!set) { + do + s = rand_global.ops->range(&rand_global, LCRAND_P); + while (s == LCRAND_FIXEDPT); + } + return (lcrand_create(s)); +} + +/* --- Basic options parser -- can't generate output --- */ + +static grand *gen_opts(unsigned i) +{ + while (opt() >= 0) + ; + return (0); +} + +/*----- Generators table --------------------------------------------------*/ + +static gen generators[] = { + { "fibonacci", gen_fib, 0, + "[-s SEED]" }, + { "lc", gen_lc, 0, + "[-s SEED]" }, + { "des-ofb", gen_ofb, OFB_DES, + "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" }, + { "3des-ofb", gen_ofb, OFB_DES3, + "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" }, + { "rc5-ofb", gen_ofb, OFB_RC5, + "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" }, + { "blowfish-ofb", gen_ofb, OFB_BLOWFISH, + "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" }, + { "idea-ofb", gen_ofb, OFB_IDEA, + "[-k KEY-PHRASE] [-H HEX-KEY] [-i HEX-IV]" }, + { "rc4", gen_rc4, 0, + "[-k KEY-PHRASE] [-H HEX-KEY]" }, + { "rand", gen_rand, 0, + "[-n] [-k KEY-PHRASE] [-t TEXT-BLOCK] [-H HEX-BLOCK]" }, + { "bbs", gen_bbs, 0, + "[-gS] [-s SEED] [-m MODULUS] [-b BITS" }, + { 0, 0, 0, 0 }, +}; + +static gen optsg = { "options", gen_opts, 0, + "This message shouldn't be printed." }; + +/*----- Main code ---------------------------------------------------------*/ + +int main(int ac, char *av[]) +{ + gen *g = &optsg; + grand *r; + unsigned percent = -1; + size_t kb = 0; + time_t last; + static char baton[] = "|/-\\"; + char *bp; + + /* --- Initialize mLib --- */ + + ego(av[0]); + sub_init(); + + /* --- Set up the main Catacomb generator --- */ + + rand_noisesrc(RAND_GLOBAL, &noise_source); + + /* --- Initialize the options table --- */ + + addopts(sopts, opts); + argc = ac; + argv = av; + + /* --- Read the generator out of the first argument --- */ + + if (argc > 1 && *argv[1] != '-') { + const char *arg = av[1]; + size_t sz = strlen(arg); + gen *gg; + + g = 0; + for (gg = generators; gg->name; gg++) { + if (strncmp(arg, gg->name, sz) == 0) { + if (gg->name[sz] == 0) { + g = gg; + break; + } else if (g) + die(EXIT_FAILURE, "ambiguous generator name `%s'", arg); + else + g = gg; + } + } + if (!g) + die(EXIT_FAILURE, "unknown generator name `%s'", arg); + argc--; + argv++; + } + + /* --- Get a generic random number generator --- */ + + r = g->seed(g->i); + if (!r || optind != ac - 1) { + usage(stderr); + exit(EXIT_FAILURE); + } + +#ifndef PORTABLE + if (!(flags & f_file) && isatty(STDOUT_FILENO)) + die(EXIT_FAILURE, "writing output to a terminal is a bad idea"); +#endif + + /* --- Spit out random data --- */ + + last = time(0); + bp = baton; + if (flags & f_progress) { + char *errbuf = xmalloc(BUFSIZ); + setvbuf(stderr, errbuf, _IOLBF, BUFSIZ); + fputc('[', stderr); + fflush(stderr); + } + +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + for (;;) { + octet buf[BUFSIZ]; + size_t sz = sizeof(buf); + + /* --- Emit a bufferful (or less) of data --- */ + + if (outsz) { + if (sz > outsz - kb) + sz = outsz - kb; + } + r->ops->fill(r, buf, sz); + if (fwrite(buf, 1, sz, outfp) != sz) { + if (flags & f_progress) + fputc('\n', stderr); + die(EXIT_FAILURE, "error writing data: %s", strerror(errno)); + } + kb += sz; + + /* --- Update the display --- */ + + if (flags & f_progress) { + time_t t = time(0); + unsigned up = 0; + + if (percent > 100) + up = 1; + + if (!outsz) { + if (difftime(t, last) > 1.0) { + up = 1; + } + if (up) + fputs(" ] ", stderr); + } else { + unsigned pc = kb * 100.0 / outsz; + if (pc > percent || percent > 100 || difftime(t, last) > 1.0) { + if (percent > 100) + percent = 0; + percent &= ~1; + for (; percent < (pc & ~1); percent += 2) + putc('.', stderr); + percent = pc; + for (; pc < 100; pc += 2) + putc(' ', stderr); + fprintf(stderr, "] %3i%% ", percent); + up = 1; + } + } + + if (up) { + size_t q = kb; + char *suff = " KMG"; + while (q > 8192 && suff[1]) { + q >>= 10; + suff++; + } + fprintf(stderr, "%4i%c\r[", q, *suff); + if (outsz) { + unsigned pc; + for (pc = 0; pc < (percent & ~1); pc += 2) + putc('.', stderr); + } + last = t; + } + + if (percent > 100) + percent = 0; + + if (percent < 100) { + putc(*bp++, stderr); + putc('\b', stderr); + if (!*bp) + bp = baton; + } + fflush(stderr); + } + + /* --- Terminate the loop --- */ + + if (outsz && kb >= outsz) + break; + } + + /* --- Done --- */ + + r->ops->destroy(r); + if (flags & f_progress) + fputc('\n', stderr); + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ -- 2.11.0