From e3d72e092411025b87c42081efefd9ef138c2012 Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Sat, 23 Jun 2012 13:40:25 +0200 Subject: [PATCH] Convert write_rec_to_block() to a state machine --- bacula/src/stored/read_record.c | 42 ++-- bacula/src/stored/record.c | 331 ++++++++++++++++++-------------- bacula/src/stored/record.h | 15 +- 3 files changed, 222 insertions(+), 166 deletions(-) diff --git a/bacula/src/stored/read_record.c b/bacula/src/stored/read_record.c index 078045d4ac..ab77400e1a 100644 --- a/bacula/src/stored/read_record.c +++ b/bacula/src/stored/read_record.c @@ -48,7 +48,7 @@ static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *s static BSR *position_to_first_file(JCR *jcr, DCR *dcr); static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr); #ifdef DEBUG -static char *rec_state_to_str(DEV_RECORD *rec); +static char *rec_state_bits_to_str(DEV_RECORD *rec); #endif static const int dbglvl = 500; @@ -189,23 +189,23 @@ bool read_records(DCR *dcr, rec = new_record(); recs->prepend(rec); Dmsg3(dbglvl, "New record for state=%s SI=%d ST=%d\n", - rec_state_to_str(rec), + rec_state_bits_to_str(rec), block->VolSessionId, block->VolSessionTime); } - Dmsg3(dbglvl, "Before read rec loop. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec), + Dmsg3(dbglvl, "Before read rec loop. stat=%s blk=%d rem=%d\n", rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder); record = 0; - rec->state = 0; + rec->state_bits = 0; lastFileIndex = no_FileIndex; Dmsg1(dbglvl, "Block %s empty\n", is_block_empty(rec)?"is":"NOT"); - for (rec->state=0; ok && !is_block_empty(rec); ) { + for (rec->state_bits=0; ok && !is_block_empty(rec); ) { if (!read_record_from_block(dcr, block, rec)) { - Dmsg3(400, "!read-break. state=%s blk=%d rem=%d\n", rec_state_to_str(rec), + Dmsg3(400, "!read-break. state_bits=%s blk=%d rem=%d\n", rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder); break; } - Dmsg5(dbglvl, "read-OK. state=%s blk=%d rem=%d file:block=%u:%u\n", - rec_state_to_str(rec), block->BlockNumber, rec->remainder, + Dmsg5(dbglvl, "read-OK. state_bits=%s blk=%d rem=%d file:block=%u:%u\n", + rec_state_bits_to_str(rec), block->BlockNumber, rec->remainder, dev->file, dev->block_num); /* * At this point, we have at least a record header. @@ -214,8 +214,8 @@ bool read_records(DCR *dcr, * get all the data. */ record++; - Dmsg6(dbglvl, "recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record, - rec_state_to_str(rec), block->BlockNumber, + Dmsg6(dbglvl, "recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record, + rec_state_bits_to_str(rec), block->BlockNumber, rec->VolSessionId, rec->VolSessionTime, rec->FileIndex); if (rec->FileIndex == EOM_LABEL) { /* end of tape? */ @@ -268,7 +268,7 @@ bool read_records(DCR *dcr, Dmsg4(dbglvl, "BSR no match: clear rem=%d FI=%d before set_eof pos %u:%u\n", rec->remainder, rec->FileIndex, dev->file, dev->block_num); rec->remainder = 0; - rec->state &= ~REC_PARTIAL_RECORD; + rec->state_bits &= ~REC_PARTIAL_RECORD; if (try_repositioning(jcr, rec, dcr)) { break; } @@ -277,14 +277,14 @@ bool read_records(DCR *dcr, } dcr->VolLastIndex = rec->FileIndex; /* let caller know where we are */ if (is_partial_record(rec)) { - Dmsg6(dbglvl, "Partial, break. recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record, - rec_state_to_str(rec), block->BlockNumber, + Dmsg6(dbglvl, "Partial, break. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record, + rec_state_bits_to_str(rec), block->BlockNumber, rec->VolSessionId, rec->VolSessionTime, rec->FileIndex); break; /* read second part of record */ } - Dmsg6(dbglvl, "OK callback. recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record, - rec_state_to_str(rec), block->BlockNumber, + Dmsg6(dbglvl, "OK callback. recno=%d state_bits=%s blk=%d SI=%d ST=%d FI=%d\n", record, + rec_state_bits_to_str(rec), block->BlockNumber, rec->VolSessionId, rec->VolSessionTime, rec->FileIndex); if (lastFileIndex != no_FileIndex && lastFileIndex != rec->FileIndex) { if (is_this_bsr_done(jcr->bsr, rec) && try_repositioning(jcr, rec, dcr)) { @@ -369,7 +369,7 @@ static bool try_repositioning(JCR *jcr, DEV_RECORD *rec, DCR *dcr) if (dev_addr > bsr_addr) { return false; } - Dmsg4(10, "Try_Reposition from (file:block) %u:%u to %u:%u\n", + Dmsg4(dbglvl, "Try_Reposition from (file:block) %u:%u to %u:%u\n", dev->file, dev->block_num, file, block); dev->reposition(dcr, file, block); rec->Block = 0; @@ -437,23 +437,23 @@ static void handle_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *s } #ifdef DEBUG -static char *rec_state_to_str(DEV_RECORD *rec) +static char *rec_state_bits_to_str(DEV_RECORD *rec) { static char buf[200]; buf[0] = 0; - if (rec->state & REC_NO_HEADER) { + if (rec->state_bits & REC_NO_HEADER) { bstrncat(buf, "Nohdr,", sizeof(buf)); } if (is_partial_record(rec)) { bstrncat(buf, "partial,", sizeof(buf)); } - if (rec->state & REC_BLOCK_EMPTY) { + if (rec->state_bits & REC_BLOCK_EMPTY) { bstrncat(buf, "empty,", sizeof(buf)); } - if (rec->state & REC_NO_MATCH) { + if (rec->state_bits & REC_NO_MATCH) { bstrncat(buf, "Nomatch,", sizeof(buf)); } - if (rec->state & REC_CONTINUATION) { + if (rec->state_bits & REC_CONTINUATION) { bstrncat(buf, "cont,", sizeof(buf)); } if (buf[0]) { diff --git a/bacula/src/stored/record.c b/bacula/src/stored/record.c index a28d9ef936..a25abad052 100644 --- a/bacula/src/stored/record.c +++ b/bacula/src/stored/record.c @@ -27,7 +27,7 @@ */ /* * - * record.c -- tape record handling functions + * record.c -- Volume (tape/disk) record handling functions * * Kern Sibbald, April MMI * added BB02 format October MMII @@ -246,6 +246,7 @@ DEV_RECORD *new_record(void) rec = (DEV_RECORD *)get_memory(sizeof(DEV_RECORD)); memset(rec, 0, sizeof(DEV_RECORD)); rec->data = get_pool_memory(PM_MESSAGE); + rec->state = st_none; return rec; } @@ -255,7 +256,8 @@ void empty_record(DEV_RECORD *rec) rec->VolSessionId = rec->VolSessionTime = 0; rec->FileIndex = rec->Stream = 0; rec->data_len = rec->remainder = 0; - rec->state &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION); + rec->state_bits &= ~(REC_PARTIAL_RECORD|REC_BLOCK_EMPTY|REC_NO_MATCH|REC_CONTINUATION); + rec->state = st_none; } /* @@ -273,92 +275,13 @@ void free_record(DEV_RECORD *rec) Dmsg0(950, "Leave free_record.\n"); } - -/* - * Write a Record to the block - * - * Returns: false on failure (none or partially written) - * true on success (all bytes written) - * - * and remainder returned in packet. - * - * We require enough room for the header, and we deal with - * two special cases. 1. Only part of the record may have - * been transferred the last time (when remainder is - * non-zero), and 2. The remaining bytes to write may not - * all fit into the block. - */ -bool write_record_to_block(DCR *dcr, DEV_RECORD *rec) +static bool write_header_to_block(DEV_BLOCK *block, DEV_RECORD *rec) { ser_declare; - uint32_t remlen; - char buf1[100], buf2[100]; - DEV_BLOCK *block; - - block = dcr->block; - - remlen = block->buf_len - block->binbuf; - - ASSERT(block->binbuf == (uint32_t) (block->bufp - block->buf)); - ASSERT(block->buf_len >= block->binbuf); - - Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n" - "rem=%d remainder=%d\n", - FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, - stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, - remlen, rec->remainder); - /* - * If rec->remainder is non-zero, we have been called a - * second (or subsequent) time to finish writing a record - * that did not previously fit into the block. - */ - if (rec->remainder == 0) { - /* Require enough room to write a full header */ - if (remlen >= WRITE_RECHDR_LENGTH) { - ser_begin(block->bufp, WRITE_RECHDR_LENGTH); - if (BLOCK_VER == 1) { - ser_uint32(rec->VolSessionId); - ser_uint32(rec->VolSessionTime); - } else { - block->VolSessionId = rec->VolSessionId; - block->VolSessionTime = rec->VolSessionTime; - } - ser_int32(rec->FileIndex); - ser_int32(rec->Stream); - ser_uint32(rec->data_len); - - block->bufp += WRITE_RECHDR_LENGTH; - block->binbuf += WRITE_RECHDR_LENGTH; - remlen -= WRITE_RECHDR_LENGTH; - rec->remainder = rec->data_len; - if (rec->FileIndex > 0) { - /* If data record, update what we have in this block */ - if (block->FirstIndex == 0) { - block->FirstIndex = rec->FileIndex; - } - block->LastIndex = rec->FileIndex; - } - } else { - rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH; - return false; - } - } else { - /* - * We are here to write unwritten bytes from a previous - * time. Presumably we have a new buffer (possibly - * containing a volume label), so the new header - * should be able to fit in the block -- otherwise we have - * an error. Note, we have to continue splitting the - * data record if it is longer than the block. - * - * First, write the header, then write as much as - * possible of the data record. - * - * Every time we write a header and it is a continuation - * of a previous partially written record, we store the - * Stream as -Stream in the record header. - */ + rec->remlen = block->buf_len - block->binbuf; + /* Require enough room to write a full header */ + if (rec->remlen >= WRITE_RECHDR_LENGTH) { ser_begin(block->bufp, WRITE_RECHDR_LENGTH); if (BLOCK_VER == 1) { ser_uint32(rec->VolSessionId); @@ -368,21 +291,13 @@ bool write_record_to_block(DCR *dcr, DEV_RECORD *rec) block->VolSessionTime = rec->VolSessionTime; } ser_int32(rec->FileIndex); - if (rec->remainder > rec->data_len) { - ser_int32(rec->Stream); /* normal full header */ - ser_uint32(rec->data_len); - rec->remainder = rec->data_len; /* must still do data record */ - } else { - ser_int32(-rec->Stream); /* mark this as a continuation record */ - ser_uint32(rec->remainder); /* bytes to do */ - } - - /* Require enough room to write a full header */ - ASSERT(remlen >= WRITE_RECHDR_LENGTH); + ser_int32(rec->Stream); + ser_uint32(rec->data_len); block->bufp += WRITE_RECHDR_LENGTH; block->binbuf += WRITE_RECHDR_LENGTH; - remlen -= WRITE_RECHDR_LENGTH; + rec->remlen -= WRITE_RECHDR_LENGTH; + rec->remainder = rec->data_len; if (rec->FileIndex > 0) { /* If data record, update what we have in this block */ if (block->FirstIndex == 0) { @@ -390,54 +305,186 @@ bool write_record_to_block(DCR *dcr, DEV_RECORD *rec) } block->LastIndex = rec->FileIndex; } + } else { + rec->remainder = rec->data_len + WRITE_RECHDR_LENGTH; + return false; } - if (remlen == 0) { - return false; /* partial transfer */ - } + return true; +} +static void write_continue_header_to_block(DEV_BLOCK *block, DEV_RECORD *rec) +{ + ser_declare; + + rec->remlen = block->buf_len - block->binbuf; /* - * Now deal with data record. - * Part of it may have already been transferred, and we - * may not have enough room to transfer the whole this time. + * We have unwritten bytes from a previous + * time. Presumably we have a new buffer (possibly + * containing a volume label), so the new header + * should be able to fit in the block -- otherwise we have + * an error. Note, we have to continue splitting the + * data record if it is longer than the block. + * + * First, write the header. + * + * Every time we write a header and it is a continuation + * of a previous partially written record, we store the + * Stream as -Stream in the record header. */ - if (rec->remainder > 0) { - /* Write as much of data as possible */ - if (remlen >= rec->remainder) { - memcpy(block->bufp, rec->data+rec->data_len-rec->remainder, - rec->remainder); - block->bufp += rec->remainder; - block->binbuf += rec->remainder; - } else { - memcpy(block->bufp, rec->data+rec->data_len-rec->remainder, - remlen); + ser_begin(block->bufp, WRITE_RECHDR_LENGTH); + if (BLOCK_VER == 1) { + ser_uint32(rec->VolSessionId); + ser_uint32(rec->VolSessionTime); + } else { + block->VolSessionId = rec->VolSessionId; + block->VolSessionTime = rec->VolSessionTime; + } + ser_int32(rec->FileIndex); + if (rec->remainder > rec->data_len) { + ser_int32(rec->Stream); /* normal full header */ + ser_uint32(rec->data_len); + rec->remainder = rec->data_len; /* must still do data record */ + } else { + ser_int32(-rec->Stream); /* mark this as a continuation record */ + ser_uint32(rec->remainder); /* bytes to do */ + } + + /* Require enough room to write a full header */ + ASSERT(rec->remlen >= WRITE_RECHDR_LENGTH); + + block->bufp += WRITE_RECHDR_LENGTH; + block->binbuf += WRITE_RECHDR_LENGTH; + rec->remlen -= WRITE_RECHDR_LENGTH; + if (rec->FileIndex > 0) { + /* If data record, update what we have in this block */ + if (block->FirstIndex == 0) { + block->FirstIndex = rec->FileIndex; + } + block->LastIndex = rec->FileIndex; + } +} + +static bool write_data_to_block(DEV_BLOCK *block, DEV_RECORD *rec) +{ + rec->remlen = block->buf_len - block->binbuf; + /* Write as much of data as possible */ + if (rec->remlen >= rec->remainder) { + memcpy(block->bufp, rec->data+rec->data_len-rec->remainder, + rec->remainder); + block->bufp += rec->remainder; + block->binbuf += rec->remainder; + } else { + memcpy(block->bufp, rec->data+rec->data_len-rec->remainder, + rec->remlen); #ifdef xxxxxSMCHECK - if (!sm_check_rtn(__FILE__, __LINE__, False)) { - /* We damaged a buffer */ - Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n" - "rem=%d remainder=%d\n", - FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, - stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, - remlen, rec->remainder); - Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n", - block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf, - remlen); - Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n", - block->buf, block->bufp-block->buf); - - Emsg0(M_ABORT, 0, _("Damaged buffer\n")); - } + if (!sm_check_rtn(__FILE__, __LINE__, False)) { + /* We damaged a buffer */ + Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n" + "rem=%d remainder=%d\n", + FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, + stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, + rec->remlen, rec->remainder); + Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n", + block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf, + rec->remlen); + Dmsg2(0, "Damaged block: buf=%x binbuffrombuf=%d \n", + block->buf, block->bufp-block->buf); + Emsg0(M_ABORT, 0, _("Damaged buffer\n")); + } #endif - block->bufp += remlen; - block->binbuf += remlen; - rec->remainder -= remlen; - return false; /* did partial transfer */ - } + block->bufp += rec->remlen; + block->binbuf += rec->remlen; + rec->remainder -= rec->remlen; + return false; /* did partial transfer */ } - rec->remainder = 0; /* did whole transfer */ return true; } +/* + * Write a Record to the block + * + * Returns: false on failure (none or partially written) + * true on success (all bytes written) + * + * and remainder returned in packet. + * + * We require enough room for the header, and we deal with + * two special cases. 1. Only part of the record may have + * been transferred the last time (when remainder is + * non-zero), and 2. The remaining bytes to write may not + * all fit into the block. + */ +bool write_record_to_block(DCR *dcr, DEV_RECORD *rec) +{ + char buf1[100], buf2[100]; + DEV_BLOCK *block = dcr->block; + for ( ;; ) { + ASSERT(block->binbuf == (uint32_t)(block->bufp - block->buf)); + ASSERT(block->buf_len >= block->binbuf); + + Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n" + "rem=%d remainder=%d\n", + FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, + stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len, + rec->remlen, rec->remainder); + + switch (rec->state) { + case st_none: + /* Figure out what to do */ + rec->state = st_header; + continue; /* go to next state */ + + case st_header: + /* + * Write header + * + * If rec->remainder is non-zero, we have been called a + * second (or subsequent) time to finish writing a record + * that did not previously fit into the block. + */ + if (!write_header_to_block(block, rec)) { + rec->state = st_header_cont; + return false; + } + rec->state = st_data; + continue; + + /* Write continuation header */ + case st_header_cont: + write_continue_header_to_block(block, rec); + rec->state = st_data; + if (rec->remlen == 0) { + return false; /* partial transfer */ + } + continue; + + /* Write normal data */ + case st_data: + /* + * Write data + * + * Part of it may have already been transferred, and we + * may not have enough room to transfer the whole this time. + */ + if (rec->remainder > 0) { + if (!write_data_to_block(block, rec)) { + rec->state = st_header_cont; + return false; + } + } + rec->remainder = 0; /* did whole transfer */ + rec->state = st_none; + return true; + + default: + Dmsg0(000, "Something went wrong. Default state.\n"); + rec->state = st_none; + return true; + } + } + return true; +} /* * Test if we can write whole record to the block @@ -494,9 +541,9 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) remlen = block->binbuf; /* Clear state flags */ - rec->state = 0; + rec->state_bits = 0; if (block->dev->is_tape()) { - rec->state |= REC_ISTAPE; + rec->state_bits |= REC_ISTAPE; } rec->Block = ((DEVICE *)block->dev)->EndBlock; rec->File = ((DEVICE *)block->dev)->EndFile; @@ -537,7 +584,7 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) */ if (rec->remainder && (rec->VolSessionId != VolSessionId || rec->VolSessionTime != VolSessionTime)) { - rec->state |= REC_NO_MATCH; + rec->state_bits |= REC_NO_MATCH; Dmsg0(450, "remainder and VolSession doesn't match\n"); return false; /* This is from some other Session */ } @@ -548,11 +595,11 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) if (Stream < 0) { /* continuation record? */ Dmsg1(500, "Got negative Stream => continuation. remainder=%d\n", rec->remainder); - rec->state |= REC_CONTINUATION; + rec->state_bits |= REC_CONTINUATION; if (!rec->remainder) { /* if we didn't read previously */ rec->data_len = 0; /* return data as if no continuation */ } else if (rec->Stream != -Stream) { - rec->state |= REC_NO_MATCH; + rec->state_bits |= REC_NO_MATCH; return false; /* This is from some other Session */ } rec->Stream = -Stream; /* set correct Stream */ @@ -587,7 +634,7 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) * then reread. */ Dmsg0(450, "read_record_block: nothing\n"); - rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY); + rec->state_bits |= (REC_NO_HEADER | REC_BLOCK_EMPTY); empty_block(block); /* mark block empty */ return false; } @@ -598,7 +645,7 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) * Something is wrong, force read of next block, abort * continuing with this block. */ - rec->state |= (REC_NO_HEADER | REC_BLOCK_EMPTY); + rec->state_bits |= (REC_NO_HEADER | REC_BLOCK_EMPTY); empty_block(block); Jmsg2(dcr->jcr, M_WARNING, 0, _("Sanity check failed. maxlen=%d datalen=%d. Block discarded.\n"), MAX_BLOCK_LENGTH, data_bytes); @@ -629,7 +676,7 @@ bool read_record_from_block(DCR *dcr, DEV_BLOCK *block, DEV_RECORD *rec) rec->data_len += remlen; rec->remainder = 1; /* partial record transferred */ Dmsg1(450, "read_record_block: partial xfered=%d\n", rec->data_len); - rec->state |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY); + rec->state_bits |= (REC_PARTIAL_RECORD | REC_BLOCK_EMPTY); return true; } rec->remainder = 0; diff --git a/bacula/src/stored/record.h b/bacula/src/stored/record.h index 92da7d3513..62c245d7d2 100644 --- a/bacula/src/stored/record.h +++ b/bacula/src/stored/record.h @@ -50,6 +50,13 @@ enum { VOL_NO_MEDIA /* Hard error -- no media present */ }; +enum rec_state { + st_none, /* No state */ + st_header, /* Write header */ + st_header_cont, + st_data, +}; + /* See block.h for RECHDR_LENGTH */ @@ -78,8 +85,8 @@ enum { #define REC_CONTINUATION (1<<4) /* Continuation record found */ #define REC_ISTAPE (1<<5) /* Set if device is tape */ -#define is_partial_record(r) ((r)->state & REC_PARTIAL_RECORD) -#define is_block_empty(r) ((r)->state & REC_BLOCK_EMPTY) +#define is_partial_record(r) ((r)->state_bits & REC_PARTIAL_RECORD) +#define is_block_empty(r) ((r)->state_bits & REC_BLOCK_EMPTY) /* * DEV_RECORD for reading and writing records. @@ -102,7 +109,9 @@ struct DEV_RECORD { int32_t maskedStream; /* Masked Stream without high bits */ uint32_t data_len; /* current record length */ uint32_t remainder; /* remaining bytes to read/write */ - uint32_t state; /* state bits */ + uint32_t remlen; /* temp remainder bytes */ + uint32_t state_bits; /* state bits */ + rec_state state; /* state of write_record_to_block */ BSR *bsr; /* pointer to bsr that matched */ uint8_t ser_buf[WRITE_RECHDR_LENGTH]; /* serialized record header goes here */ POOLMEM *data; /* Record data. This MUST be a memory pool item */ -- 2.39.5