Example #1
0
/**
 * Creates an incremental zlib deflater for `len' bytes starting at `data',
 * with specified compression `level'.
 *
 * @param data		data to compress; if NULL, will be incrementally given
 * @param len		length of data to compress (if data not NULL) or estimation
 * @param dest		where compressed data should go, or NULL if allocated
 * @param destlen	length of supplied output buffer, if dest != NULL
 * @param level		compression level, between 0 and 9.
 *
 * @return new deflater, or NULL if error.
 */
static zlib_deflater_t *
zlib_deflater_alloc(
    const void *data, size_t len, void *dest, size_t destlen, int level)
{
    zlib_deflater_t *zd;
    z_streamp outz;
    int ret;

    g_assert(size_is_non_negative(len));
    g_assert(size_is_non_negative(destlen));
    g_assert(level == Z_DEFAULT_COMPRESSION || (level >= 0 && level <= 9));

    WALLOC(outz);
    outz->zalloc = zlib_alloc_func;
    outz->zfree = zlib_free_func;
    outz->opaque = NULL;

    ret = deflateInit(outz, level);

    if (ret != Z_OK) {
        WFREE(outz);
        g_carp("%s(): unable to initialize compressor: %s",
               G_STRFUNC, zlib_strerror(ret));
        return NULL;
    }

    WALLOC0(zd);
    zd->zs.magic = ZLIB_DEFLATER_MAGIC;
    zd->zs.z = outz;
    zd->zs.closed = FALSE;

    zlib_stream_init(&zd->zs, data, len, dest, destlen);

    return zd;
}
Example #2
0
/**
 * Get rid of the driver's private data.
 */
static void
tx_deflate_destroy(txdrv_t *tx)
{
	struct attr *attr = tx->opaque;
	int i;
	int ret;

	g_assert(attr->outz);

	for (i = 0; i < BUFFER_COUNT; i++) {
		struct buffer *b = &attr->buf[i];
		wfree(b->arena, attr->buffer_size);
	}

	/*
	 * We ignore Z_DATA_ERROR errors (discarded data, probably).
	 */

	ret = deflateEnd(attr->outz);

	if (Z_OK != ret && Z_DATA_ERROR != ret)
		g_warning("while freeing compressor for peer %s: %s",
			gnet_host_to_string(&tx->host), zlib_strerror(ret));

	WFREE(attr->outz);
	cq_cancel(&attr->tm_ev);
	WFREE(attr);
}
Example #3
0
static bool decompress_with_fastlz(JCR *jcr,
                                   const char *last_fname,
                                   char **data,
                                   uint32_t *length,
                                   uint32_t comp_magic,
                                   bool sparse,
                                   bool want_data_stream)
{
   int zstat;
   zfast_stream stream;
   zfast_stream_compressor compressor = COMPRESSOR_FASTLZ;
   char ec1[50]; /* Buffer printing huge values */

   switch (comp_magic) {
   case COMPRESS_FZ4L:
   case COMPRESS_FZ4H:
      compressor = COMPRESSOR_LZ4;
      break;
   }

   /*
    * NOTE! We only use uInt and Bytef because they are
    * needed by the fastlz routines, they should not otherwise
    * be used in Bareos.
    */
   memset(&stream, 0, sizeof(stream));
   stream.next_in = (Bytef *)*data + sizeof(comp_stream_header);
   stream.avail_in = (uInt)*length - sizeof(comp_stream_header);
   if (sparse && want_data_stream) {
      stream.next_out = (Bytef *)jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE;
      stream.avail_out = (uInt)jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE;
   } else {
      stream.next_out = (Bytef *)jcr->compress.inflate_buffer;
      stream.avail_out = (uInt)jcr->compress.inflate_buffer_size;
   }

   Dmsg2(400, "Comp_len=%d msglen=%d\n", stream.avail_in, *length);

   if ((zstat = fastlzlibDecompressInit(&stream)) != Z_OK) {
      goto cleanup;
   }

   if ((zstat = fastlzlibSetCompressor(&stream, compressor)) != Z_OK) {
      goto cleanup;
   }

   while (1) {
      zstat = fastlzlibDecompress(&stream);
      switch (zstat) {
      case Z_BUF_ERROR:
         /*
          * The buffer size is too small, try with a bigger one
          */
         jcr->compress.inflate_buffer_size = jcr->compress.inflate_buffer_size + (jcr->compress.inflate_buffer_size >> 1);
         jcr->compress.inflate_buffer = check_pool_memory_size(jcr->compress.inflate_buffer, jcr->compress.inflate_buffer_size);
         if (sparse && want_data_stream) {
            stream.next_out = (Bytef *)jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE;
            stream.avail_out = (uInt)jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE;
         } else {
            stream.next_out = (Bytef *)jcr->compress.inflate_buffer;
            stream.avail_out = (uInt)jcr->compress.inflate_buffer_size;
         }
         continue;
      case Z_OK:
      case Z_STREAM_END:
         break;
      default:
         goto cleanup;
      }
      break;
   }

   /*
    * We return a decompressed data stream with the fileoffset encoded when this was a sparse stream.
    */
   if (sparse && want_data_stream) {
      memcpy(jcr->compress.inflate_buffer, *data, OFFSET_FADDR_SIZE);
   }

   *data = jcr->compress.inflate_buffer;
   *length = stream.total_out;
   Dmsg2(400, "Write uncompressed %d bytes, total before write=%s\n", *length, edit_uint64(jcr->JobBytes, ec1));
   fastlzlibDecompressEnd(&stream);

   return true;

cleanup:
   Qmsg(jcr, M_ERROR, 0, _("Uncompression error on file %s. ERR=%s\n"), last_fname, zlib_strerror(zstat));
   fastlzlibDecompressEnd(&stream);

   return false;
}
Example #4
0
static bool decompress_with_zlib(JCR *jcr,
                                 const char *last_fname,
                                 char **data,
                                 uint32_t *length,
                                 bool sparse,
                                 bool with_header,
                                 bool want_data_stream)
{
   char ec1[50]; /* Buffer printing huge values */
   uLong compress_len;
   const unsigned char *cbuf;
   char *wbuf;
   int status, real_compress_len;

   /*
    * NOTE! We only use uLong and Byte because they are
    * needed by the zlib routines, they should not otherwise
    * be used in Bareos.
    */
   if (sparse && want_data_stream) {
      wbuf = jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE;
      compress_len = jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE;
   } else {
      wbuf = jcr->compress.inflate_buffer;
      compress_len = jcr->compress.inflate_buffer_size;
   }

   /*
    * See if this is a compressed stream with the new compression header or an old one.
    */
   if (with_header) {
      cbuf = (const unsigned char*)*data + sizeof(comp_stream_header);
      real_compress_len = *length - sizeof(comp_stream_header);
   } else {
      cbuf = (const unsigned char*)*data;
      real_compress_len = *length;
   }

   Dmsg2(400, "Comp_len=%d msglen=%d\n", compress_len, *length);

   while ((status = uncompress((Byte *)wbuf, &compress_len, (const Byte *)cbuf, (uLong)real_compress_len)) == Z_BUF_ERROR) {
      /*
       * The buffer size is too small, try with a bigger one
       */
      jcr->compress.inflate_buffer_size = jcr->compress.inflate_buffer_size + (jcr->compress.inflate_buffer_size >> 1);
      jcr->compress.inflate_buffer = check_pool_memory_size(jcr->compress.inflate_buffer, jcr->compress.inflate_buffer_size);

      if (sparse && want_data_stream) {
         wbuf = jcr->compress.inflate_buffer + OFFSET_FADDR_SIZE;
         compress_len = jcr->compress.inflate_buffer_size - OFFSET_FADDR_SIZE;
      } else {
         wbuf = jcr->compress.inflate_buffer;
         compress_len = jcr->compress.inflate_buffer_size;
      }
      Dmsg2(400, "Comp_len=%d msglen=%d\n", compress_len, *length);
   }

   if (status != Z_OK) {
      Qmsg(jcr, M_ERROR, 0, _("Uncompression error on file %s. ERR=%s\n"), last_fname, zlib_strerror(status));
      return false;
   }

   /*
    * We return a decompressed data stream with the fileoffset encoded when this was a sparse stream.
    */
   if (sparse && want_data_stream) {
      memcpy(jcr->compress.inflate_buffer, *data, OFFSET_FADDR_SIZE);
   }

   *data = jcr->compress.inflate_buffer;
   *length = compress_len;

   Dmsg2(400, "Write uncompressed %d bytes, total before write=%s\n", compress_len, edit_uint64(jcr->JobBytes, ec1));

   return true;
}
Example #5
0
/**
 * Incrementally process more data.
 *
 * @param zs		the zlib stream object
 * @param amount	amount of data to process
 * @param maxout	maximum length of dynamically-allocated buffer (0 = none)
 * @param may_close	whether to allow closing when all data was consumed
 * @param finish	whether this is the last data to process
 *
 * @return -1 on error, 1 if work remains, 0 when done.
 */
static int
zlib_stream_process_step(zlib_stream_t *zs, int amount, size_t maxout,
                         bool may_close, bool finish)
{
    z_streamp z;
    int remaining;
    int process;
    bool finishing;
    int ret = 0;

    g_assert(amount > 0);
    g_assert(!zs->closed);

    z = zs->z;
    g_assert(z != NULL);			/* Stream not closed yet */

    /*
     * Compute amount of input data to process.
     */

    remaining = zs->inlen - ptr_diff(z->next_in, zs->in);
    g_assert(remaining >= 0);

    process = MIN(remaining, amount);
    finishing = process == remaining;

    /*
     * Process data.
     */

    z->avail_in = process;

resume:
    switch (zs->magic) {
    case ZLIB_DEFLATER_MAGIC:
        ret = deflate(z, finishing && finish ? Z_FINISH : 0);
        break;
    case ZLIB_INFLATER_MAGIC:
        ret = inflate(z, Z_SYNC_FLUSH);
        break;
    }

    switch (ret) {
    case Z_OK:
        if (0 == z->avail_out) {
            if (zlib_stream_grow_output(zs, maxout))
                goto resume;	/* Process remaining input */
            goto error;			/* Cannot continue */
        }
        return 1;				/* Need to call us again */
    /* NOTREACHED */

    case Z_BUF_ERROR:			/* Output full or need more input to continue */
        if (0 == z->avail_out) {
            if (zlib_stream_grow_output(zs, maxout))
                goto resume;	/* Process remaining input */
            goto error;			/* Cannot continue */
        }
        if (0 == z->avail_in)
            return 1;			/* Need to call us again */
        goto error;				/* Cannot continue */
    /* NOTREACHED */

    case Z_STREAM_END:			/* Reached end of input stream */
        g_assert(finishing);

        /*
         * Supersede the output length to let them probe how much data
         * was processed once the stream is closed, through calls to
         * zlib_deflater_outlen() or zlib_inflater_outlen().
         */

        zs->outlen = ptr_diff(z->next_out, zs->out);
        g_assert(zs->outlen > 0);

        if (may_close) {
            switch (zs->magic) {
            case ZLIB_DEFLATER_MAGIC:
                ret = deflateEnd(z);
                break;
            case ZLIB_INFLATER_MAGIC:
                ret = inflateEnd(z);
                break;
            }

            if (ret != Z_OK) {
                g_carp("%s(): while freeing zstream: %s",
                       G_STRFUNC, zlib_strerror(ret));
            }
            WFREE(z);
            zs->z = NULL;
        }

        zs->closed = TRUE;		/* Signals processing stream done */
        return 0;				/* Done */
    /* NOTREACHED */

    default:
        break;
    }

    /* FALL THROUGH */

error:
    g_carp("%s(): error during %scompression: %s "
           "(avail_in=%u, avail_out=%u, total_in=%lu, total_out=%lu)",
           G_STRFUNC, ZLIB_DEFLATER_MAGIC == zs->magic ? "" : "de",
           zlib_strerror(ret),
           z->avail_in, z->avail_out, z->total_in, z->total_out);

    if (may_close) {
        switch (zs->magic) {
        case ZLIB_DEFLATER_MAGIC:
            ret = deflateEnd(z);
        case ZLIB_INFLATER_MAGIC:
            ret = inflateEnd(z);
            break;
        }
        if (ret != Z_OK && ret != Z_DATA_ERROR) {
            g_carp("%s(): while freeing stream: %s",
                   G_STRFUNC, zlib_strerror(ret));
        }
        WFREE(z);
        zs->z = NULL;
    }

    return -1;				/* Error! */
}
Example #6
0
/**
 * Initialize the driver.
 *
 * @return NULL if there is an initialization problem.
 */
static void *
tx_deflate_init(txdrv_t *tx, void *args)
{
	struct attr *attr;
	struct tx_deflate_args *targs = args;
	z_streamp outz;
	int ret;
	int i;

	g_assert(tx);
	g_assert(NULL != targs->cb);

	WALLOC(outz);
	outz->zalloc = zlib_alloc_func;
	outz->zfree = zlib_free_func;
	outz->opaque = NULL;

	/*
	 * Reduce memory requirements for deflation when running as an ultrapeer.
	 *
	 * Memory used for deflation is:
	 *
	 *	(1 << (window_bits +2)) +  (1 << (mem_level + 9))
	 *
	 * For leaves, we use window_bits = 15 and mem_level = 9, which makes
	 * for 128 KiB + 256 KiB = 384 KiB per connection (TX side).
	 *
	 * For ultra peers, we use window_bits = 14 and mem_level = 6, so this
	 * uses 64 KiB + 32 KiB = 96 KiB only.
	 *
	 * Since ultra peers have many more connections than leaves, the memory
	 * savings are drastic, yet compression levels remain around 50% (varies
	 * depending on the nature of the traffic, of course).
	 *
	 *		--RAM, 2009-04-09
	 *
	 * For Ultra <-> Ultra connections we use window_bits = 15 and mem_level = 9
	 * and request a best compression because the amount of ultra connections
	 * is far less than the number of leaf connections and modern machines
	 * can cope with a "best" compression overhead.
	 *
	 * This is now controlled with the "reduced" argument, so this layer does
	 * not need to know whether we're an ultra node or even what an ultra
	 * node is... It just knows whether we have to setup a fully compressed
	 * connection or a reduced one (both in terms of memory usage and level
	 * of compression).
	 *
	 *		--RAM, 2011-11-29
	 */

	{
		int window_bits = MAX_WBITS;		/* Must be 8 .. MAX_WBITS */
		int mem_level = MAX_MEM_LEVEL;		/* Must be 1 .. MAX_MEM_LEVEL */
		int level = Z_BEST_COMPRESSION;

		if (targs->reduced) {
			/* Ultra -> Leaf connection */
			window_bits = 14;
			mem_level = 6;
			level = Z_DEFAULT_COMPRESSION;
		}

		g_assert(window_bits >= 8 && window_bits <= MAX_WBITS);
		g_assert(mem_level >= 1 && mem_level <= MAX_MEM_LEVEL);
		g_assert(level == Z_DEFAULT_COMPRESSION ||
			(level >= Z_BEST_SPEED && level <= Z_BEST_COMPRESSION));

		ret = deflateInit2(outz, level, Z_DEFLATED,
				targs->gzip ? (-window_bits) : window_bits, mem_level,
				Z_DEFAULT_STRATEGY);
	}

	if (Z_OK != ret) {
		g_warning("unable to initialize compressor for peer %s: %s",
			gnet_host_to_string(&tx->host), zlib_strerror(ret));
		WFREE(outz);
		return NULL;
	}

	WALLOC0(attr);
	attr->cq = targs->cq;
	attr->cb = targs->cb;
	attr->buffer_size = targs->buffer_size;
	attr->buffer_flush = targs->buffer_flush;
	attr->nagle = booleanize(targs->nagle);
	attr->gzip.enabled = targs->gzip;

	attr->outz = outz;
	attr->tm_ev = NULL;

	for (i = 0; i < BUFFER_COUNT; i++) {
		struct buffer *b = &attr->buf[i];

		b->arena = b->wptr = b->rptr = walloc(attr->buffer_size);
		b->end = &b->arena[attr->buffer_size];
	}

	attr->fill_idx = 0;
	attr->send_idx = -1;		/* Signals: none ready */

	if (attr->gzip.enabled) {
		/* See RFC 1952 - GZIP file format specification version 4.3 */
		static const unsigned char header[] = {
			0x1f, 0x8b, /* gzip magic */
			0x08,		/* compression method: deflate */
			0,			/* flags: none */
			0, 0, 0, 0, /* modification time: unavailable */
			0,			/* extra flags: none */
			0xff,		/* filesystem: unknown */
		};
		struct buffer *b;

		b = &attr->buf[attr->fill_idx];	/* Buffer we fill */
		g_assert(sizeof header <= (size_t) (b->end - b->wptr));
		b->wptr = mempcpy(b->wptr, header, sizeof header);

		attr->gzip.crc = crc32(0, NULL, 0);
		attr->gzip.size = 0;
	}

	tx->opaque = attr;

	/*
	 * Register our service routine to the lower layer.
	 */

	tx_srv_register(tx->lower, deflate_service, tx);

	return tx;		/* OK */
}
Example #7
0
/**
 * Compress as much data as possible to the output buffer, sending data
 * as we go along.
 *
 * @return the amount of input bytes that were consumed ("added"), -1 on error.
 */
static int
deflate_add(txdrv_t *tx, const void *data, int len)
{
	struct attr *attr = tx->opaque;
	z_streamp outz = attr->outz;
	int added = 0;

	if (tx_deflate_debugging(9)) {
		g_debug("TX %s: (%s) given %u bytes (buffer #%d, nagle %s, "
			"unflushed %zu) [%c%c]%s", G_STRFUNC,
			gnet_host_to_string(&tx->host), len, attr->fill_idx,
			(attr->flags & DF_NAGLE) ? "on" : "off", attr->unflushed,
			(attr->flags & DF_FLOWC) ? 'C' : '-',
			(attr->flags & DF_FLUSH) ? 'f' : '-',
			(tx->flags & TX_ERROR) ? " ERROR" : "");
	}

	/*
	 * If an error was already reported, the whole deflate stream is dead
	 * and we cannot accept any more data.
	 */

	if G_UNLIKELY(tx->flags & TX_ERROR)
		return -1;

	while (added < len) {
		struct buffer *b = &attr->buf[attr->fill_idx];	/* Buffer we fill */
		int ret;
		int old_added = added;
		bool flush_started = (attr->flags & DF_FLUSH) ? TRUE : FALSE;
		int old_avail;
		const char *in, *old_in;

		/*
		 * Prepare call to deflate().
		 */

		outz->next_out = cast_to_pointer(b->wptr);
		outz->avail_out = old_avail = b->end - b->wptr;

		in = data;
		old_in = &in[added];
		outz->next_in = deconstify_pointer(old_in);
		outz->avail_in = len - added;

		g_assert(outz->avail_out > 0);
		g_assert(outz->avail_in > 0);

		/*
		 * Compress data.
		 *
		 * If we previously started to flush, continue the operation, now
		 * that we have more room available for the output.
		 */

		ret = deflate(outz, flush_started ? Z_SYNC_FLUSH : 0);

		if (Z_OK != ret) {
			attr->flags |= DF_SHUTDOWN;
			(*attr->cb->shutdown)(tx->owner, "Compression failed: %s",
				zlib_strerror(ret));
			return -1;
		}

		/*
		 * Update the parameters.
		 */

		b->wptr = cast_to_pointer(outz->next_out);
		added = ptr_diff(outz->next_in, in);

		g_assert(added >= old_added);

		attr->unflushed += added - old_added;
		attr->flushed += old_avail - outz->avail_out;

		if (NULL != attr->cb->add_tx_deflated)
			attr->cb->add_tx_deflated(tx->owner, old_avail - outz->avail_out);

		if (attr->gzip.enabled) {
			size_t r;

			r = ptr_diff(outz->next_in, old_in);
			attr->gzip.size += r;
			attr->gzip.crc = crc32(attr->gzip.crc,
								cast_to_constpointer(old_in), r);
		}

		if (tx_deflate_debugging(9)) {
			g_debug("TX %s: (%s) deflated %d bytes into %d "
				"(buffer #%d, nagle %s, flushed %zu, unflushed %zu) [%c%c]",
				G_STRFUNC, gnet_host_to_string(&tx->host),
				added, old_avail - outz->avail_out, attr->fill_idx,
				(attr->flags & DF_NAGLE) ? "on" : "off",
				attr->flushed, attr->unflushed,
				(attr->flags & DF_FLOWC) ? 'C' : '-',
				(attr->flags & DF_FLUSH) ? 'f' : '-');
		}

		/*
		 * If we filled the output buffer, check whether we have a pending
		 * send buffer.  If we do, we cannot process more data.  Otherwise
		 * send it now and continue.
		 */

		if (0 == outz->avail_out) {
			if (attr->send_idx >= 0) {
				deflate_set_flowc(tx, TRUE);	/* Enter flow control */
				return added;
			}

			deflate_rotate_and_send(tx);		/* Can set TX_ERROR */

			if (tx->flags & TX_ERROR)
				return -1;
		}

		/*
		 * If we were flushing and we consumed all the input, then
		 * the flush is done and we're starting normal compression again.
		 *
		 * This must be done after we made sure that we had enough output
		 * space avaialable.
		 */

		if (flush_started && 0 == outz->avail_in)
			deflate_flushed(tx);
	}

	g_assert(0 == outz->avail_in);

	/*
	 * Start Nagle if not already on.
	 */

	if (attr->flags & DF_NAGLE)
		deflate_nagle_delay(tx);
	else
		deflate_nagle_start(tx);

	/*
	 * We're going to ask for a flush if not already started yet and the
	 * amount of bytes we have written since the last flush is greater
	 * than attr->buffer_flush.
	 */

	if (attr->unflushed > attr->buffer_flush) {
		if (!deflate_flush(tx))
			return -1;
	}

	return added;
}
Example #8
0
/**
 * Flush compression within filling buffer.
 *
 * @return success status, failure meaning we shutdown.
 */
static bool
deflate_flush(txdrv_t *tx)
{
	struct attr *attr = tx->opaque;
	z_streamp outz = attr->outz;
	struct buffer *b;
	int ret;
	int old_avail;

retry:
	b = &attr->buf[attr->fill_idx];	/* Buffer we fill */

	if (tx_deflate_debugging(9)) {
		g_debug("TX %s: (%s) flushing %zu bytes "
			"(buffer #%d, flushed %zu, unflushed %zu) [%c%c]",
			G_STRFUNC, gnet_host_to_string(&tx->host),
			b->wptr - b->rptr, attr->fill_idx,
			attr->flushed, attr->unflushed,
			(attr->flags & DF_FLOWC) ? 'C' : '-',
			(attr->flags & DF_FLUSH) ? 'f' : '-');
	}

	/*
	 * Prepare call to deflate().
	 *
	 * We force avail_in to 0, and don't touch next_in: no input should
	 * be consumed.
	 */

	outz->next_out = cast_to_pointer(b->wptr);
	outz->avail_out = old_avail = b->end - b->wptr;

	outz->avail_in = 0;

	g_assert(outz->avail_out > 0);

	ret = deflate(outz, (tx->flags & TX_CLOSING) ? Z_FINISH : Z_SYNC_FLUSH);

	switch (ret) {
	case Z_BUF_ERROR:				/* Nothing to flush */
		goto done;
	case Z_OK:
	case Z_STREAM_END:
		break;
	default:
		attr->flags |= DF_SHUTDOWN;
		tx_error(tx);

		/* XXX: The callback must not destroy the tx! */
		(*attr->cb->shutdown)(tx->owner, "Compression flush failed: %s",
				zlib_strerror(ret));
		return FALSE;
	}

	{
		size_t written;

		written = old_avail - outz->avail_out;
		b->wptr += written;
		attr->flushed += written;

		if (NULL != attr->cb->add_tx_deflated)
			attr->cb->add_tx_deflated(tx->owner, written);
	}

	/*
	 * Check whether avail_out is 0.
	 *
	 * If it is, then we lacked room to complete the flush.  Try to send the
	 * buffer and continue.
	 */

	if (0 == outz->avail_out) {
		if (attr->send_idx >= 0) {			/* Send buffer not sent yet */
			attr->flags |= DF_FLUSH;		/* In flush mode */
			deflate_set_flowc(tx, TRUE);	/* Starting flow-control */
			return TRUE;
		}

		deflate_rotate_and_send(tx);		/* Can set TX_ERROR */

		if (tx->flags & TX_ERROR)
			return FALSE;

		goto retry;
	}

done:
	deflate_flushed(tx);

	return TRUE;		/* Fully flushed */
}