libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

tar_extractor.c (23254B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2026 Vidyut Samanta and Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19  */
     20 /**
     21  * @file plugins/tar_extractor.c
     22  * @brief plugin to support tar files
     23  * @author Christian Grothoff
     24  *
     25  * There is already an `archive' plugin wrapping libarchive, but it is
     26  * optional and reports what is *in* the archive rather than who made
     27  * it.  This one is unconditional, reads nothing but the 512-byte member
     28  * headers, and concentrates on the ownership fields, which leak the
     29  * usernames, group names and numeric ids of the machine the archive was
     30  * created on -- exactly the provenance a first pass wants.
     31  *
     32  * References: POSIX.1-1988 ustar, GNU tar's extensions and POSIX.1-2001
     33  * pax extended headers.
     34  */
     35 #include "platform.h"
     36 #include "extractor.h"
     37 #include "forensics.h"
     38 
     39 /**
     40  * Size of a tar header and of the blocks the archive is padded to.
     41  */
     42 #define TAR_BLOCK 512
     43 
     44 /**
     45  * Largest number of member headers we will walk.  Each one costs a
     46  * seek and a 512-byte read, so this bounds us at 128 KiB of reads even
     47  * for an archive with a million members; we report a characterisation,
     48  * not an inventory.
     49  */
     50 #define TAR_MAX_MEMBERS 256
     51 
     52 /**
     53  * How many *distinct* values of a repeating attribute (owner, group,
     54  * mode) we remember and report.  Archives are made by one or two users;
     55  * more than a handful means the value is not identifying anyway.
     56  */
     57 #define TAR_MAX_DISTINCT 16
     58 
     59 /**
     60  * Longest GNU long name (typeflag 'L'/'K') payload we will read.  The
     61  * field it replaces is 100 bytes, so anything past this is a path no
     62  * filesystem would accept.
     63  */
     64 #define TAR_MAX_LONGNAME 1024
     65 
     66 /**
     67  * Largest pax extended header payload we will read and parse.
     68  */
     69 #define TAR_MAX_PAX 4096
     70 
     71 
     72 /**
     73  * What the ownership fields of the archive looked like, accumulated
     74  * over the members we walked.
     75  */
     76 struct tar_state
     77 {
     78   /**
     79    * Distinct owner user names, NUL-terminated.
     80    */
     81   char unames[TAR_MAX_DISTINCT][33];
     82 
     83   /**
     84    * Distinct owner group names, NUL-terminated.
     85    */
     86   char gnames[TAR_MAX_DISTINCT][33];
     87 
     88   /**
     89    * Distinct numeric user ids.
     90    */
     91   uint64_t uids[TAR_MAX_DISTINCT];
     92 
     93   /**
     94    * Distinct numeric group ids.
     95    */
     96   uint64_t gids[TAR_MAX_DISTINCT];
     97 
     98   /**
     99    * Distinct permission bits.
    100    */
    101   uint64_t modes[TAR_MAX_DISTINCT];
    102 
    103   /**
    104    * Number of entries used in @e unames.
    105    */
    106   unsigned int n_unames;
    107 
    108   /**
    109    * Number of entries used in @e gnames.
    110    */
    111   unsigned int n_gnames;
    112 
    113   /**
    114    * Number of entries used in @e uids.
    115    */
    116   unsigned int n_uids;
    117 
    118   /**
    119    * Number of entries used in @e gids.
    120    */
    121   unsigned int n_gids;
    122 
    123   /**
    124    * Number of entries used in @e modes.
    125    */
    126   unsigned int n_modes;
    127 };
    128 
    129 
    130 /**
    131  * Is @a block entirely zero?  Two such blocks end an archive; we stop
    132  * at the first, which is what every reader does in practice.
    133  *
    134  * @param block the 512 bytes to check
    135  * @return 1 if all bytes are zero
    136  */
    137 static int
    138 is_zero_block (const unsigned char *block)
    139 {
    140   for (unsigned int i = 0; i < TAR_BLOCK; i++)
    141     if (0 != block[i])
    142       return 0;
    143   return 1;
    144 }
    145 
    146 
    147 /**
    148  * Verify the header checksum of @a block.
    149  *
    150  * The checksum is the sum of all 512 header bytes with the checksum
    151  * field itself read as spaces.  Historical tars disagreed on whether
    152  * the bytes are signed, so accept either reading.
    153  *
    154  * @param block the header block
    155  * @return 1 if the stored checksum matches
    156  */
    157 static int
    158 checksum_ok (const unsigned char *block)
    159 {
    160   uint64_t stored;
    161   uint32_t usum = 0;
    162   int32_t ssum = 0;
    163 
    164   if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[148],
    165                                          8,
    166                                          &stored))
    167     return 0;
    168   for (unsigned int i = 0; i < TAR_BLOCK; i++)
    169   {
    170     if ( (i >= 148) &&
    171          (i < 156) )
    172     {
    173       usum += (uint32_t) ' ';
    174       ssum += (int32_t) ' ';
    175       continue;
    176     }
    177     usum += (uint32_t) block[i];
    178     ssum += (int32_t) (signed char) block[i];
    179   }
    180   return ( (stored == (uint64_t) usum) ||
    181            ( (ssum > 0) &&
    182              (stored == (uint64_t) ssum) ) );
    183 }
    184 
    185 
    186 /**
    187  * Read a numeric header field.
    188  *
    189  * Fields are normally NUL- or space-terminated octal.  GNU tar escapes
    190  * values that do not fit by setting the high bit of the first byte and
    191  * storing the rest base-256, big-endian; we accept the positive form
    192  * and refuse the rest rather than misparse it.
    193  *
    194  * @param field the field
    195  * @param len width of the field
    196  * @param[out] value where to store the result
    197  * @return 1 on success, 0 if the field cannot be read
    198  */
    199 static int
    200 tar_number (const unsigned char *field,
    201             size_t len,
    202             uint64_t *value)
    203 {
    204   if (0 == len)
    205     return 0;
    206   if (0 != (field[0] & 0x80))
    207   {
    208     uint64_t v = 0;
    209     size_t i = 1;
    210 
    211     if (0x80 != field[0])
    212       return 0;   /* 0xff is the negative form; nothing else is defined */
    213     while ( (i < len) &&
    214             (0 == field[i]) )
    215       i++;
    216     if (len - i > 8)
    217       return 0;   /* does not fit in 64 bits */
    218     for (; i < len; i++)
    219       v = (v << 8) | field[i];
    220     *value = v;
    221     return 1;
    222   }
    223   if (0 == EXTRACTOR_forensic_trim_ ((const char *) field,
    224                                      len))
    225   {
    226     /* an all-NUL field is how several tars spell "zero" */
    227     *value = 0;
    228     return 1;
    229   }
    230   return EXTRACTOR_forensic_parse_octal_ ((const char *) field,
    231                                           len,
    232                                           value);
    233 }
    234 
    235 
    236 /**
    237  * Could @a block be a header of a pre-POSIX (v7) tar?
    238  *
    239  * v7 has no magic at all, so the only defence against claiming every
    240  * file in the world is to insist that the fields we do understand make
    241  * sense.  The caller has already verified the checksum.
    242  *
    243  * @param block the header block
    244  * @return 1 if this looks like a v7 header
    245  */
    246 static int
    247 plausible_v7 (const unsigned char *block)
    248 {
    249   uint64_t v;
    250 
    251   if ('\0' == block[0])
    252     return 0;   /* a member with no name */
    253   for (unsigned int i = 0; i < 100; i++)
    254   {
    255     if ('\0' == block[i])
    256       break;
    257     if ( (block[i] < 0x20) ||
    258          (0x7f == block[i]) )
    259       return 0;   /* control characters in a file name */
    260   }
    261   if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[100],
    262                                          8,
    263                                          &v))
    264     return 0;   /* mode */
    265   if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[136],
    266                                          12,
    267                                          &v))
    268     return 0;   /* mtime */
    269   switch (block[156])
    270   {
    271   case '\0':
    272   case '0':
    273   case '1':
    274   case '2':
    275   case '3':
    276   case '4':
    277   case '5':
    278   case '6':
    279   case '7':
    280     return 1;
    281   default:
    282     return 0;
    283   }
    284 }
    285 
    286 
    287 /**
    288  * Remember @a value if it is new.
    289  *
    290  * @param seen array of values seen so far
    291  * @param[in,out] count number of used slots in @a seen
    292  * @param value the value to record
    293  * @return 1 if @a value had not been seen before and was recorded
    294  */
    295 static int
    296 add_distinct_num (uint64_t *seen,
    297                   unsigned int *count,
    298                   uint64_t value)
    299 {
    300   for (unsigned int i = 0; i < *count; i++)
    301     if (seen[i] == value)
    302       return 0;
    303   if (*count >= TAR_MAX_DISTINCT)
    304     return 0;
    305   seen[(*count)++] = value;
    306   return 1;
    307 }
    308 
    309 
    310 /**
    311  * Remember the fixed-width string @a value if it is new and not empty.
    312  *
    313  * @param seen array of 33-byte slots seen so far
    314  * @param[in,out] count number of used slots in @a seen
    315  * @param value the field, not necessarily NUL-terminated
    316  * @param len width of the field, at most 32
    317  * @return 1 if @a value had not been seen before and was recorded
    318  */
    319 static int
    320 add_distinct_str (char seen[][33],
    321                   unsigned int *count,
    322                   const char *value,
    323                   size_t len)
    324 {
    325   char tmp[33];
    326 
    327   if (len > sizeof (tmp) - 1)
    328     len = sizeof (tmp) - 1;
    329   memcpy (tmp,
    330           value,
    331           len);
    332   tmp[len] = '\0';
    333   len = strlen (tmp);
    334   len = EXTRACTOR_forensic_trim_ (tmp,
    335                                   len);
    336   tmp[len] = '\0';
    337   if (0 == len)
    338     return 0;
    339   for (unsigned int i = 0; i < *count; i++)
    340     if (0 == strcmp (seen[i],
    341                      tmp))
    342       return 0;
    343   if (*count >= TAR_MAX_DISTINCT)
    344     return 0;
    345   memcpy (seen[(*count)++],
    346           tmp,
    347           len + 1);
    348   return 1;
    349 }
    350 
    351 
    352 /**
    353  * Pull the overriding path and link target out of a pax extended
    354  * header payload.
    355  *
    356  * The payload is a sequence of "<len> <key>=<value>\n" records where
    357  * @a len counts the whole record including its own digits and the
    358  * newline.  We only care about the two keys that override a field we
    359  * would otherwise report truncated; a zero or nonsensical length ends
    360  * the parse so that a crafted payload cannot spin here.
    361  *
    362  * @param data the payload
    363  * @param len number of bytes in @a data
    364  * @param[out] path buffer of #TAR_MAX_LONGNAME + 1 bytes for "path"
    365  * @param[out] linkpath buffer of #TAR_MAX_LONGNAME + 1 bytes for
    366  *        "linkpath"
    367  */
    368 static void
    369 parse_pax (const char *data,
    370            size_t len,
    371            char *path,
    372            char *linkpath)
    373 {
    374   size_t pos = 0;
    375 
    376   while (pos < len)
    377   {
    378     size_t reclen = 0;
    379     size_t digits = 0;
    380     size_t key;
    381     size_t eq;
    382     char *dst = NULL;
    383 
    384     while ( (pos + digits < len) &&
    385             ('0' <= data[pos + digits]) &&
    386             ('9' >= data[pos + digits]) )
    387     {
    388       if (reclen > TAR_MAX_PAX)
    389         return;   /* absurd record length */
    390       reclen = reclen * 10 + (size_t) (data[pos + digits] - '0');
    391       digits++;
    392     }
    393     if ( (0 == digits) ||
    394          (reclen <= digits + 1) ||
    395          (reclen > len - pos) )
    396       return;   /* no forward progress possible */
    397     key = pos + digits + 1;   /* skip the space after the length */
    398     for (eq = key; eq < pos + reclen; eq++)
    399       if ('=' == data[eq])
    400         break;
    401     if (eq < pos + reclen)
    402     {
    403       size_t klen = eq - key;
    404 
    405       if ( (4 == klen) &&
    406            (0 == memcmp (&data[key],
    407                          "path",
    408                          4)) )
    409         dst = path;
    410       if ( (8 == klen) &&
    411            (0 == memcmp (&data[key],
    412                          "linkpath",
    413                          8)) )
    414         dst = linkpath;
    415       if (NULL != dst)
    416       {
    417         size_t vlen = pos + reclen - eq - 1;
    418 
    419         if ( (vlen > 0) &&
    420              ('\n' == data[eq + vlen]) )
    421           vlen--;   /* drop the record terminator */
    422         if (vlen > TAR_MAX_LONGNAME)
    423           vlen = TAR_MAX_LONGNAME;
    424         memcpy (dst,
    425                 &data[eq + 1],
    426                 vlen);
    427         dst[vlen] = '\0';
    428       }
    429     }
    430     pos += reclen;
    431   }
    432 }
    433 
    434 
    435 /**
    436  * Main entry method for the tar extraction plugin.
    437  *
    438  * @param ec extraction context provided to the plugin
    439  */
    440 void
    441 EXTRACTOR_tar_extract_method (struct EXTRACTOR_ExtractContext *ec);
    442 
    443 void
    444 EXTRACTOR_tar_extract_method (struct EXTRACTOR_ExtractContext *ec)
    445 {
    446   unsigned char block[TAR_BLOCK];
    447   struct tar_state st;
    448   char longname[TAR_MAX_LONGNAME + 1];
    449   char longlink[TAR_MAX_LONGNAME + 1];
    450   char name[TAR_BLOCK];
    451   const char *format;
    452   uint64_t file_size;
    453   uint64_t offset = 0;
    454   uint64_t total_size = 0;
    455   int64_t newest = 0;
    456   unsigned int members = 0;
    457   unsigned int names_emitted = 0;
    458   unsigned int links_emitted = 0;
    459   int have_ustar;
    460   int truncated_walk = 0;
    461 
    462   if (! EXTRACTOR_forensic_read_ (ec,
    463                                   0,
    464                                   block,
    465                                   TAR_BLOCK))
    466     return;   /* too short for even one header */
    467   have_ustar = (0 == memcmp (&block[257],
    468                              "ustar",
    469                              5)) &&
    470                ( ('\0' == block[262]) ||
    471                  (' ' == block[262]) );
    472   if (have_ustar)
    473   {
    474     /* POSIX writes "ustar\0" followed by "00"; GNU writes "ustar  \0" */
    475     format = (' ' == block[262]) ? "gnu" : "ustar";
    476   }
    477   else
    478   {
    479     if (! checksum_ok (block))
    480       return;   /* not a tar */
    481     if (! plausible_v7 (block))
    482       return;   /* not a tar */
    483     format = "v7";
    484   }
    485   if (0 !=
    486       EXTRACTOR_forensic_emit_ (ec,
    487                                 "tar",
    488                                 EXTRACTOR_METATYPE_MIMETYPE,
    489                                 "%s",
    490                                 "application/x-tar"))
    491     return;
    492   memset (&st,
    493           0,
    494           sizeof (st));
    495   longname[0] = '\0';
    496   longlink[0] = '\0';
    497   file_size = ec->get_size (ec->cls);
    498   while (members < TAR_MAX_MEMBERS)
    499   {
    500     uint64_t size;
    501     uint64_t mode;
    502     uint64_t num;
    503     unsigned char typeflag;
    504 
    505     if ( (UINT64_MAX != file_size) &&
    506          (offset + TAR_BLOCK > file_size) )
    507       break;
    508     if (0 != offset)
    509     {
    510       if (! EXTRACTOR_forensic_read_ (ec,
    511                                       (int64_t) offset,
    512                                       block,
    513                                       TAR_BLOCK))
    514         break;
    515     }
    516     if (is_zero_block (block))
    517       break;   /* end-of-archive marker */
    518     if (! checksum_ok (block))
    519       break;   /* corrupt or not a member header after all */
    520     if (! tar_number (&block[124],
    521                       12,
    522                       &size))
    523       break;   /* unreadable size: we can no longer find the next header */
    524     if ( (size > UINT64_MAX - 2 * TAR_BLOCK) ||
    525          ( (UINT64_MAX != file_size) &&
    526            (size > file_size) ) )
    527       break;   /* would overflow the walk or run past the file */
    528     typeflag = block[156];
    529     switch (typeflag)
    530     {
    531     case 'x':
    532     case 'g':
    533       {
    534         char pax[TAR_MAX_PAX];
    535         size_t want = (size > TAR_MAX_PAX) ? TAR_MAX_PAX : (size_t) size;
    536 
    537         /* pax extended header; the payload is metadata for the member
    538            whose header comes next, not a member of its own */
    539         format = "pax";
    540         if ( (0 != want) &&
    541              (EXTRACTOR_forensic_read_ (ec,
    542                                         (int64_t) (offset + TAR_BLOCK),
    543                                         pax,
    544                                         want)) )
    545           parse_pax (pax,
    546                      want,
    547                      longname,
    548                      longlink);
    549         break;
    550       }
    551     case 'L':
    552     case 'K':
    553       {
    554         size_t want = (size > TAR_MAX_LONGNAME) ? TAR_MAX_LONGNAME
    555                       : (size_t) size;
    556 
    557         /* GNU long name/link: the payload is the real name of the
    558            member whose header comes next */
    559         format = "gnu";
    560         if ( (0 != want) &&
    561              (EXTRACTOR_forensic_read_ (ec,
    562                                         (int64_t) (offset + TAR_BLOCK),
    563                                         ('L' == typeflag) ? longname
    564                                         : longlink,
    565                                         want)) )
    566         {
    567           if ('L' == typeflag)
    568             longname[want] = '\0';
    569           else
    570             longlink[want] = '\0';
    571         }
    572         break;
    573       }
    574     default:
    575       {
    576         const char *member_name;
    577         size_t member_len;
    578 
    579         members++;
    580         /* regular files carry content; everything else has size 0 or a
    581            size that does not describe stored bytes */
    582         if ( ('0' == typeflag) ||
    583              ('\0' == typeflag) ||
    584              ('7' == typeflag) )
    585           total_size += size;
    586         if ('\0' != longname[0])
    587         {
    588           member_name = longname;
    589           member_len = strlen (longname);
    590         }
    591         else if ( (have_ustar) &&
    592                   ('\0' != block[345]) )
    593         {
    594           size_t plen;
    595           size_t nlen;
    596 
    597           /* ustar splits long paths as prefix + '/' + name */
    598           plen = strnlen ((const char *) &block[345],
    599                           155);
    600           nlen = strnlen ((const char *) &block[0],
    601                           100);
    602           if (plen > sizeof (name) - 2)
    603             plen = sizeof (name) - 2;
    604           if (nlen > sizeof (name) - 2 - plen)
    605             nlen = sizeof (name) - 2 - plen;
    606           memcpy (name,
    607                   &block[345],
    608                   plen);
    609           name[plen] = '/';
    610           memcpy (&name[plen + 1],
    611                   block,
    612                   nlen);
    613           name[plen + 1 + nlen] = '\0';
    614           member_name = name;
    615           member_len = plen + 1 + nlen;
    616         }
    617         else
    618         {
    619           member_name = (const char *) block;
    620           member_len = strnlen ((const char *) block,
    621                                 100);
    622         }
    623         if (names_emitted < EXTRACTOR_FORENSIC_MAX_ITEMS)
    624         {
    625           names_emitted++;
    626           if (0 !=
    627               EXTRACTOR_forensic_emit_text_ (ec,
    628                                              "tar",
    629                                              EXTRACTOR_METATYPE_FILENAME,
    630                                              member_name,
    631                                              member_len))
    632             return;
    633         }
    634         if ( ( ('1' == typeflag) ||
    635                ('2' == typeflag) ) &&
    636              (links_emitted < EXTRACTOR_FORENSIC_MAX_ITEMS) )
    637         {
    638           const char *target;
    639           size_t target_len;
    640 
    641           if ('\0' != longlink[0])
    642           {
    643             target = longlink;
    644             target_len = strlen (longlink);
    645           }
    646           else
    647           {
    648             target = (const char *) &block[157];
    649             target_len = strnlen (target,
    650                                   100);
    651           }
    652           links_emitted++;
    653           if (0 !=
    654               EXTRACTOR_forensic_emit_text_ (ec,
    655                                              "tar",
    656                                              EXTRACTOR_METATYPE_TARGET_PATH,
    657                                              target,
    658                                              target_len))
    659             return;
    660         }
    661         /* -- ownership: the reason this plugin exists -- */
    662         if ( (have_ustar) &&
    663              (add_distinct_str (st.unames,
    664                                 &st.n_unames,
    665                                 (const char *) &block[265],
    666                                 32)) &&
    667              (0 != EXTRACTOR_forensic_emit_text_ (ec,
    668                                                   "tar",
    669                                                   EXTRACTOR_METATYPE_OWNER_USER,
    670                                                   (const char *) &block[265],
    671                                                   32)) )
    672           return;
    673         if ( (have_ustar) &&
    674              (add_distinct_str (st.gnames,
    675                                 &st.n_gnames,
    676                                 (const char *) &block[297],
    677                                 32)) &&
    678              (0 != EXTRACTOR_forensic_emit_text_ (ec,
    679                                                   "tar",
    680                                                   EXTRACTOR_METATYPE_OWNER_GROUP,
    681                                                   (const char *) &block[297],
    682                                                   32)) )
    683           return;
    684         if ( (tar_number (&block[108],
    685                           8,
    686                           &num)) &&
    687              (add_distinct_num (st.uids,
    688                                 &st.n_uids,
    689                                 num)) &&
    690              (0 != EXTRACTOR_forensic_emit_ (ec,
    691                                              "tar",
    692                                              EXTRACTOR_METATYPE_OWNER_UID,
    693                                              "%llu",
    694                                              (unsigned long long) num)) )
    695           return;
    696         if ( (tar_number (&block[116],
    697                           8,
    698                           &num)) &&
    699              (add_distinct_num (st.gids,
    700                                 &st.n_gids,
    701                                 num)) &&
    702              (0 != EXTRACTOR_forensic_emit_ (ec,
    703                                              "tar",
    704                                              EXTRACTOR_METATYPE_OWNER_GID,
    705                                              "%llu",
    706                                              (unsigned long long) num)) )
    707           return;
    708         if ( (tar_number (&block[100],
    709                           8,
    710                           &mode)) &&
    711              (add_distinct_num (st.modes,
    712                                 &st.n_modes,
    713                                 mode & 07777)) &&
    714              (0 != EXTRACTOR_forensic_emit_ (ec,
    715                                              "tar",
    716                                              EXTRACTOR_METATYPE_PERMISSIONS,
    717                                              "0%llo",
    718                                              (unsigned long long) (mode
    719                                                                    & 07777))) )
    720           return;
    721         if ( (tar_number (&block[136],
    722                           12,
    723                           &num)) &&
    724              (num < (uint64_t) INT64_MAX) &&
    725              (((int64_t) num) > newest) )
    726           newest = (int64_t) num;
    727         longname[0] = '\0';
    728         longlink[0] = '\0';
    729         break;
    730       }
    731     }
    732     offset += TAR_BLOCK + ((size + TAR_BLOCK - 1) / TAR_BLOCK) * TAR_BLOCK;
    733   }
    734   if (members >= TAR_MAX_MEMBERS)
    735     truncated_walk = 1;
    736   if (0 !=
    737       EXTRACTOR_forensic_emit_ (ec,
    738                                 "tar",
    739                                 EXTRACTOR_METATYPE_FORMAT,
    740                                 "%s",
    741                                 format))
    742     return;
    743   if (0 !=
    744       EXTRACTOR_forensic_emit_ (ec,
    745                                 "tar",
    746                                 EXTRACTOR_METATYPE_ENTRY_COUNT,
    747                                 "%u",
    748                                 members))
    749     return;
    750   if (0 !=
    751       EXTRACTOR_forensic_emit_size_ (ec,
    752                                      "tar",
    753                                      EXTRACTOR_METATYPE_UNCOMPRESSED_SIZE,
    754                                      total_size))
    755     return;
    756   /* the newest member: when the archive was assembled, near enough */
    757   if (0 !=
    758       EXTRACTOR_forensic_emit_unix_time_ (ec,
    759                                           "tar",
    760                                           EXTRACTOR_METATYPE_MODIFICATION_DATE,
    761                                           newest))
    762     return;
    763   if ( (truncated_walk) &&
    764        (0 != EXTRACTOR_forensic_emit_ (ec,
    765                                        "tar",
    766                                        EXTRACTOR_METATYPE_COMMENT,
    767                                        "member walk stopped at the cap of %u;"
    768                                        " counts and sizes are lower bounds",
    769                                        (unsigned int) TAR_MAX_MEMBERS)) )
    770     return;
    771 }
    772 
    773 
    774 /* end of tar_extractor.c */