Back to home page

LXR

 
 

    


File indexing completed on 2025-05-11 08:24:20

0001 /* SPDX-License-Identifier: BSD-2-Clause */
0002 
0003 /**
0004  * @file
0005  *
0006  * @brief Untar an Image
0007  *
0008  * @ingroup libmisc_untar_img Untar Image
0009  */
0010 
0011 /*
0012  *  Written by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
0013  *
0014  *  Copyright 2016 Chris Johns <chrisj@rtems.org>
0015  *
0016  * Redistribution and use in source and binary forms, with or without
0017  * modification, are permitted provided that the following conditions
0018  * are met:
0019  * 1. Redistributions of source code must retain the above copyright
0020  *    notice, this list of conditions and the following disclaimer.
0021  * 2. Redistributions in binary form must reproduce the above copyright
0022  *    notice, this list of conditions and the following disclaimer in the
0023  *    documentation and/or other materials provided with the distribution.
0024  *
0025  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0026  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0027  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0028  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0029  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0030  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0031  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0032  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0033  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0034  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0035  * POSSIBILITY OF SUCH DAMAGE.
0036  */
0037 
0038 #ifdef HAVE_CONFIG_H
0039 #include "config.h"
0040 #endif
0041 
0042 #include <stdbool.h>
0043 #include <sys/param.h>
0044 #include <stdio.h>
0045 #include <string.h>
0046 #include <stdlib.h>
0047 #include <unistd.h>
0048 #include <errno.h>
0049 #include <sys/stat.h>
0050 #include <fcntl.h>
0051 #include <rtems/untar.h>
0052 #include <rtems/bspIo.h>
0053 
0054 /*
0055  * TAR file format:
0056 
0057  *   Offset   Length   Contents
0058  *     0    100 bytes  File name ('\0' terminated, 99 maxmum length)
0059  *   100      8 bytes  File mode (in octal ascii)
0060  *   108      8 bytes  User ID (in octal ascii)
0061  *   116      8 bytes  Group ID (in octal ascii)
0062  *   124     12 bytes  File size (s) (in octal ascii)
0063  *   136     12 bytes  Modify time (in octal ascii)
0064  *   148      8 bytes  Header checksum (in octal ascii)
0065  *   156      1 bytes  Link flag
0066  *   157    100 bytes  Linkname ('\0' terminated, 99 maxmum length)
0067  *   257      8 bytes  Magic PAX ("ustar\0" + 2 bytes padding)
0068  *   257      8 bytes  Magic GNU tar ("ustar  \0")
0069  *   265     32 bytes  User name ('\0' terminated, 31 maxmum length)
0070  *   297     32 bytes  Group name ('\0' terminated, 31 maxmum length)
0071  *   329      8 bytes  Major device ID (in octal ascii)
0072  *   337      8 bytes  Minor device ID (in octal ascii)
0073  *   345    155 bytes  Prefix
0074  *   512   (s+p)bytes  File contents (s+p) := (((s) + 511) & ~511),
0075  *                     round up to 512 bytes
0076  *
0077  *   Checksum:
0078  *   int i, sum;
0079  *   char* header = tar_header_pointer;
0080  *   sum = 0;
0081  *   for(i = 0; i < 512; i++)
0082  *       sum += 0xFF & header[i];
0083  */
0084 
0085 #define MAX_NAME_FIELD_SIZE      99
0086 
0087 #define TAR_BLOCK_SIZE           512
0088 #define TAR_BLOCK_SIZE_MASK      (TAR_BLOCK_SIZE - 1)
0089 
0090 /*
0091  * Number of blocks read or written
0092  */
0093 #define TAR_WORK_BLOCKS          16
0094 
0095 static int _rtems_tar_header_checksum(const char *bufr);
0096 
0097 /*
0098  * This converts octal ASCII number representations into an
0099  * unsigned long.  Only support 32-bit numbers for now.
0100  */
0101 static unsigned long
0102 _rtems_octal2ulong(
0103   const char *octascii,
0104   size_t len
0105 )
0106 {
0107   size_t        i;
0108   unsigned long num;
0109 
0110   num = 0;
0111   for (i=0; i < len; i++) {
0112     if ((octascii[i] < '0') || (octascii[i] > '9')) {
0113       continue;
0114     }
0115     num  = num * 8 + ((unsigned long)(octascii[i] - '0'));
0116   }
0117   return(num);
0118 }
0119 
0120 /*
0121  * Common error message formatter.
0122  */
0123 static void
0124 Print_Error(const rtems_printer *printer, const char* message, const char* path)
0125 {
0126   rtems_printf(printer, "untar: %s: %s: (%d) %s\n",
0127                message, path, errno, strerror(errno));
0128 }
0129 
0130 /*
0131  * Make the directory path for a file if it does not exist.
0132  */
0133 static int
0134 Make_Path(const rtems_printer *printer, char *path)
0135 {
0136   char *p;
0137 
0138   /*
0139    * Skip leading path separators.
0140    */
0141   while (*path == '/') {
0142     ++path;
0143   }
0144 
0145   p = path;
0146 
0147   for (; ; ++p) {
0148     if (p[0] == '\0') {
0149       return 0;
0150     } else if (p[0] != '/') {
0151       continue;
0152     }
0153 
0154     *p = '\0';
0155     if (p[1] == '\0') {
0156       return 0;
0157     }
0158 
0159     if (mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
0160       if (errno == EEXIST) {
0161         /* If it exists already: Check whether it is a directory */
0162         struct stat sb;
0163         if (lstat(path, &sb) != 0) {
0164           Print_Error(printer, "lstat", path);
0165           return -1;
0166         } else if (!S_ISDIR(sb.st_mode)) {
0167           rtems_printf(printer,
0168                        "untar: mkdir: %s: exists but is not a directory\n",
0169                        path);
0170           return -1;
0171         }
0172       } else {
0173         Print_Error(printer, "mkdir", path);
0174         return -1;
0175       }
0176     }
0177 
0178     *p = '/';
0179   }
0180 
0181   return 0;
0182 }
0183 
0184 int
0185 Untar_ProcessHeader(
0186   Untar_HeaderContext *ctx,
0187   const char          *bufr
0188 )
0189 {
0190   int sum;
0191   int hdr_chksum;
0192   int retval = UNTAR_SUCCESSFUL;
0193   int r;
0194 
0195   ctx->file_name[0] = '\0';
0196   ctx->file_size = 0;
0197   ctx->nblocks = 0;
0198   ctx->linkflag = -1;
0199 
0200   if (strncmp(&bufr[257], "ustar", 5)) {
0201     return UNTAR_SUCCESSFUL;
0202   }
0203 
0204   /*
0205    * Compute the TAR checksum and check with the value in the archive.  The
0206    * checksum is computed over the entire header, but the checksum field is
0207    * substituted with blanks.
0208    */
0209   hdr_chksum = _rtems_octal2ulong(&bufr[148], 8);
0210   sum        = _rtems_tar_header_checksum(bufr);
0211 
0212   if (sum != hdr_chksum) {
0213     rtems_printf(ctx->printer, "untar: file header checksum error\n");
0214     return UNTAR_INVALID_CHECKSUM;
0215   }
0216 
0217   strlcpy(ctx->file_name, bufr, UNTAR_FILE_NAME_SIZE);
0218 
0219   ctx->mode = strtoul(&bufr[100], NULL, 8);
0220 
0221   ctx->linkflag   = bufr[156];
0222   ctx->file_size = _rtems_octal2ulong(&bufr[124], 12);
0223 
0224   /*
0225    * We've decoded the header, now figure out what it contains and do something
0226    * with it.
0227    */
0228 
0229   if (Make_Path(ctx->printer, ctx->file_path) != 0) {
0230     retval = UNTAR_FAIL;
0231   } else {
0232     /*
0233      * Speculatively unlink. This should unlink everything but non-empty
0234      * directories or write protected stuff.
0235      */
0236     unlink(ctx->file_path);
0237   }
0238 
0239   if (ctx->linkflag == SYMTYPE) {
0240     strlcpy(ctx->link_name, &bufr[157], sizeof(ctx->link_name));
0241     rtems_printf(ctx->printer, "untar: symlink: %s -> %s\n",
0242                  ctx->link_name, ctx->file_path);
0243     r = symlink(ctx->link_name, ctx->file_path);
0244     if (r != 0) {
0245       Print_Error(ctx->printer, "symlink", ctx->file_path);
0246       retval = UNTAR_FAIL;
0247     }
0248   } else if (ctx->linkflag == REGTYPE) {
0249     rtems_printf(ctx->printer, "untar: file: %s (s:%lu,m:%04lo)\n",
0250                  ctx->file_path, ctx->file_size, ctx->mode);
0251     ctx->nblocks =
0252       (((ctx->file_size) + TAR_BLOCK_SIZE_MASK) & ~TAR_BLOCK_SIZE_MASK) / TAR_BLOCK_SIZE;
0253   } else if (ctx->linkflag == DIRTYPE) {
0254     rtems_printf(ctx->printer, "untar: dir: %s\n", ctx->file_path);
0255     r = mkdir(ctx->file_path, ctx->mode);
0256     if (r != 0) {
0257       if (errno == EEXIST) {
0258         /* If it exists already: Check whether it is a directory */
0259         struct stat sb;
0260         if (lstat(ctx->file_path, &sb) != 0) {
0261           Print_Error(ctx->printer, "lstat", ctx->file_path);
0262           retval = UNTAR_FAIL;
0263         } else if (!S_ISDIR(sb.st_mode)) {
0264           rtems_printf(ctx->printer,
0265                        "untar: mkdir: %s: exists but is not a directory\n",
0266                        ctx->file_path);
0267           retval = UNTAR_FAIL;
0268         }
0269       } else {
0270         Print_Error(ctx->printer, "mkdir", ctx->file_path);
0271         retval = UNTAR_FAIL;
0272       }
0273     }
0274   }
0275 
0276   return retval;
0277 }
0278 
0279 /*
0280  * Function: Untar_FromMemory
0281  *
0282  * Description:
0283  *
0284  *    This is a simple subroutine used to rip links, directories, and
0285  *    files out of a block of memory.
0286  *
0287  *
0288  * Inputs:
0289  *
0290  *    void *  tar_buf    - Pointer to TAR buffer.
0291  *    size_t  size       - Length of TAR buffer.
0292  *
0293  *
0294  * Output:
0295  *
0296  *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
0297  *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
0298  *          UNTAR_INVALID_HEADER    for an invalid header.
0299  *
0300  */
0301 int
0302 Untar_FromMemory_Print(
0303   void                *tar_buf,
0304   size_t               size,
0305   const rtems_printer *printer
0306 )
0307 {
0308   int                  fd;
0309   const char          *tar_ptr = (const char *)tar_buf;
0310   const char          *bufr;
0311   char                 buf[UNTAR_FILE_NAME_SIZE];
0312   Untar_HeaderContext  ctx;
0313   int                  retval = UNTAR_SUCCESSFUL;
0314   unsigned long        ptr;
0315 
0316   ctx.file_path = buf;
0317   ctx.file_name = buf;
0318   ctx.printer = printer;
0319   rtems_printf(printer, "untar: memory at %p (%zu)\n", tar_buf, size);
0320 
0321   ptr = 0;
0322   while (true) {
0323     if (ptr + TAR_BLOCK_SIZE > size) {
0324       retval = UNTAR_SUCCESSFUL;
0325       break;
0326     }
0327 
0328     /* Read the header */
0329     bufr = &tar_ptr[ptr];
0330     ptr += TAR_BLOCK_SIZE;
0331 
0332     retval = Untar_ProcessHeader(&ctx, bufr);
0333 
0334     if (retval != UNTAR_SUCCESSFUL)
0335       break;
0336 
0337     if (ctx.linkflag == REGTYPE) {
0338       if ((fd = open(ctx.file_path,
0339                      O_TRUNC | O_CREAT | O_WRONLY, ctx.mode)) == -1) {
0340         Print_Error(printer, "open", ctx.file_path);
0341         ptr += TAR_BLOCK_SIZE * ctx.nblocks;
0342       } else {
0343         /*
0344          * Read out the data.  There are nblocks of data where nblocks is the
0345          * file_size rounded to the nearest 512-byte boundary.
0346          */
0347         ssize_t file_size = ctx.file_size;
0348         size_t blocks = ctx.nblocks;
0349         while (blocks > 0) {
0350           size_t blks = blocks > TAR_WORK_BLOCKS ? TAR_WORK_BLOCKS : blocks;
0351           ssize_t len = MIN(blks * TAR_BLOCK_SIZE, file_size);
0352           ssize_t n = write(fd, &tar_ptr[ptr], len);
0353           if (n != len) {
0354             Print_Error(printer, "write", ctx.file_path);
0355             retval  = UNTAR_FAIL;
0356             break;
0357           }
0358           ptr += blks * TAR_BLOCK_SIZE;
0359           file_size -= n;
0360           blocks -= blks;
0361         }
0362         close(fd);
0363       }
0364 
0365     }
0366   }
0367 
0368   return retval;
0369 }
0370 
0371 /*
0372  * Function: Untar_FromMemory
0373  *
0374  * Description:
0375  *
0376  *    This is a simple subroutine used to rip links, directories, and
0377  *    files out of a block of memory.
0378  *
0379  *
0380  * Inputs:
0381  *
0382  *    void *  tar_buf    - Pointer to TAR buffer.
0383  *    size_t  size       - Length of TAR buffer.
0384  *
0385  *
0386  * Output:
0387  *
0388  *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
0389  *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
0390  *          UNTAR_INVALID_HEADER    for an invalid header.
0391  *
0392  */
0393 int
0394 Untar_FromMemory(
0395   void   *tar_buf,
0396   size_t  size
0397 )
0398 {
0399   return Untar_FromMemory_Print(tar_buf, size, false);
0400 }
0401 
0402 /*
0403  * Function: Untar_FromFile
0404  *
0405  * Description:
0406  *
0407  *    This is a simple subroutine used to rip links, directories, and
0408  *    files out of a TAR file.
0409  *
0410  * Inputs:
0411  *
0412  *    const char *tar_name   - TAR filename.
0413  *
0414  * Output:
0415  *
0416  *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
0417  *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
0418  *          UNTAR_INVALID_HEADER    for an invalid header.
0419  */
0420 int
0421 Untar_FromFile_Print(
0422   const char          *tar_name,
0423   const rtems_printer *printer
0424 )
0425 {
0426   int                  fd;
0427   char                *bufr;
0428   ssize_t              n;
0429   int                  retval;
0430   char                 buf[UNTAR_FILE_NAME_SIZE];
0431   Untar_HeaderContext  ctx;
0432 
0433   retval = UNTAR_SUCCESSFUL;
0434 
0435   if ((fd = open(tar_name, O_RDONLY)) < 0) {
0436     return UNTAR_FAIL;
0437   }
0438 
0439   bufr = (char *)malloc(TAR_WORK_BLOCKS * TAR_BLOCK_SIZE);
0440   if (bufr == NULL) {
0441     close(fd);
0442     return(UNTAR_FAIL);
0443   }
0444 
0445   ctx.file_path = buf;
0446   ctx.file_name = buf;
0447   ctx.printer = printer;
0448 
0449   while (1) {
0450     /* Read the header */
0451     /* If the header read fails, we just consider it the end of the tarfile. */
0452     if ((n = read(fd, bufr, TAR_BLOCK_SIZE)) != TAR_BLOCK_SIZE) {
0453       break;
0454     }
0455 
0456     retval = Untar_ProcessHeader(&ctx, bufr);
0457 
0458     if (retval != UNTAR_SUCCESSFUL)
0459       break;
0460 
0461     if (ctx.linkflag == REGTYPE) {
0462       int out_fd;
0463 
0464       /*
0465        * Read out the data.  There are nblocks of data where nblocks
0466        * is the size rounded to the nearest 512-byte boundary.
0467        */
0468 
0469       if ((out_fd = creat(ctx.file_path, ctx.mode)) == -1) {
0470         /* Couldn't create that file. Abort. */
0471         retval = UNTAR_FAIL;
0472         break;
0473       } else {
0474         ssize_t file_size = ctx.file_size;
0475         size_t blocks = ctx.nblocks;
0476         while (blocks > 0) {
0477           size_t blks = blocks > TAR_WORK_BLOCKS ? TAR_WORK_BLOCKS : blocks;
0478           size_t bytes = blks * TAR_BLOCK_SIZE;
0479           n = read(fd, bufr, bytes);
0480           n = MIN(n, file_size);
0481           (void) write(out_fd, bufr, n);
0482           file_size -= n;
0483           blocks -= blks;
0484         }
0485         close(out_fd);
0486       }
0487     }
0488   }
0489 
0490   free(bufr);
0491   close(fd);
0492 
0493   return retval;
0494 }
0495 
0496 
0497 void Untar_ChunkContext_Init(Untar_ChunkContext *context)
0498 {
0499   context->base.file_path = context->buf;
0500   context->base.file_name = context->buf;
0501   context->state = UNTAR_CHUNK_HEADER;
0502   context->done_bytes = 0;
0503   context->out_fd = -1;
0504 }
0505 
0506 int Untar_FromChunk_Print(
0507   Untar_ChunkContext *context,
0508   void *chunk,
0509   size_t chunk_size,
0510   const rtems_printer* printer
0511 )
0512 {
0513   char *buf;
0514   size_t done;
0515   size_t todo;
0516   size_t remaining;
0517   size_t consume;
0518   int retval;
0519 
0520   buf = chunk;
0521   done = 0;
0522   todo = chunk_size;
0523 
0524   context->base.printer = printer;
0525 
0526   while (todo > 0) {
0527     switch (context->state) {
0528       case UNTAR_CHUNK_HEADER:
0529         remaining = 512 - context->done_bytes;
0530         consume = MIN(remaining, todo);
0531         memcpy(&context->header[context->done_bytes], &buf[done], consume);
0532         context->done_bytes += consume;
0533 
0534         if (context->done_bytes == 512) {
0535           retval = Untar_ProcessHeader(
0536             &context->base,
0537             &context->header[0]
0538           );
0539 
0540           if (retval != UNTAR_SUCCESSFUL) {
0541             context->state = UNTAR_CHUNK_ERROR;
0542             return retval;
0543           }
0544 
0545           if (context->base.linkflag == REGTYPE) {
0546             context->out_fd = creat(context->base.file_path,
0547                                     context->base.mode);
0548 
0549             if (context->out_fd >= 0) {
0550               context->state = UNTAR_CHUNK_WRITE;
0551               context->done_bytes = 0;
0552             } else {
0553               context->state = UNTAR_CHUNK_SKIP;
0554               context->base.file_size = 512 * context->base.nblocks;
0555               context->done_bytes = 0;
0556             }
0557           } else {
0558               context->done_bytes = 0;
0559           }
0560         }
0561 
0562         break;
0563       case UNTAR_CHUNK_SKIP:
0564         remaining = context->base.file_size - context->done_bytes;
0565         consume = MIN(remaining, todo);
0566         context->done_bytes += consume;
0567 
0568         if (context->done_bytes == context->base.file_size) {
0569           context->state = UNTAR_CHUNK_HEADER;
0570           context->done_bytes = 0;
0571         }
0572 
0573         break;
0574       case UNTAR_CHUNK_WRITE:
0575         remaining = context->base.file_size - context->done_bytes;
0576         consume = MIN(remaining, todo);
0577         write(context->out_fd, &buf[done], consume);
0578         context->done_bytes += consume;
0579 
0580         if (context->done_bytes == context->base.file_size) {
0581           close(context->out_fd);
0582           context->out_fd = -1;
0583           context->state = UNTAR_CHUNK_SKIP;
0584           context->base.file_size = 512 * context->base.nblocks
0585             - context->base.file_size;
0586           context->done_bytes = 0;
0587         }
0588 
0589         break;
0590       default:
0591         return UNTAR_FAIL;
0592     }
0593 
0594     done += consume;
0595     todo -= consume;
0596   }
0597 
0598   return UNTAR_SUCCESSFUL;
0599 }
0600 
0601 /*
0602  * Function: Untar_FromFile
0603  *
0604  * Description:
0605  *
0606  *    This is a simple subroutine used to rip links, directories, and
0607  *    files out of a TAR file.
0608  *
0609  * Inputs:
0610  *
0611  *    const char *tar_name   - TAR filename.
0612  *
0613  * Output:
0614  *
0615  *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
0616  *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
0617  *          UNTAR_INVALID_HEADER    for an invalid header.
0618  */
0619 int
0620 Untar_FromFile(
0621   const char *tar_name
0622 )
0623 {
0624   return Untar_FromFile_Print(tar_name, NULL);
0625 }
0626 
0627 /*
0628  * Compute the TAR checksum and check with the value in
0629  * the archive.  The checksum is computed over the entire
0630  * header, but the checksum field is substituted with blanks.
0631  */
0632 static int
0633 _rtems_tar_header_checksum(
0634   const char *bufr
0635 )
0636 {
0637   int  i, sum;
0638 
0639   sum = 0;
0640   for (i=0; i<512; i++) {
0641     if ((i >= 148) && (i < 156))
0642       sum += 0xff & ' ';
0643     else
0644      sum += 0xff & bufr[i];
0645   }
0646   return(sum);
0647 }