/** * MojoSetup; a portable, flexible installation application. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. */ // Specs for the tar format can be found here... // http://www.gnu.org/software/tar/manual/html_section/Standard.html #include "fileio.h" #if !SUPPORT_TAR MojoArchive *MojoArchive_createTAR(MojoInput *io) { return NULL; } #else // MojoInput implementation... // Decompression is handled in the parent MojoInput, so this just needs to // make sure we stay within the bounds of the tarfile entry. typedef struct TARinput { int64 fsize; int64 offset; MojoArchive *ar; } TARinput; typedef struct TARinfo { MojoInput *input; uint64 curFileStart; uint64 nextEnumPos; } TARinfo; static boolean MojoInput_tar_ready(MojoInput *io) { return true; // !!! FIXME: ready if there are bytes uncompressed. } // MojoInput_tar_ready static int64 MojoInput_tar_read(MojoInput *io, void *buf, uint32 bufsize) { TARinput *input = (TARinput *) io->opaque; int64 pos = io->tell(io); if ((pos + bufsize) > input->fsize) bufsize = (uint32) (input->fsize - pos); return input->ar->io->read(input->ar->io, buf, bufsize); } // MojoInput_tar_read static boolean MojoInput_tar_seek(MojoInput *io, uint64 pos) { TARinput *input = (TARinput *) io->opaque; boolean retval = false; if (pos < ((uint64) input->fsize)) retval = input->ar->io->seek(input->ar->io, input->offset + pos); return retval; } // MojoInput_tar_seek static int64 MojoInput_tar_tell(MojoInput *io) { TARinput *input = (TARinput *) io->opaque; return input->ar->io->tell(input->ar->io) - input->offset; } // MojoInput_tar_tell static int64 MojoInput_tar_length(MojoInput *io) { return ((TARinput *) io->opaque)->fsize; } // MojoInput_tar_length static MojoInput *MojoInput_tar_duplicate(MojoInput *io) { MojoInput *retval = NULL; fatal(_("BUG: Can't duplicate tar inputs")); // !!! FIXME: why not? #if 0 TARinput *input = (TARinput *) io->opaque; MojoInput *origio = (MojoInput *) io->opaque; MojoInput *newio = origio->duplicate(origio); if (newio != NULL) { TARinput *newopaque = (TARinput *) xmalloc(sizeof (TARinput)); newopaque->origio = newio; newopaque->fsize = input->fsize; newopaque->offset = input->offset; retval = (MojoInput *) xmalloc(sizeof (MojoInput)); memcpy(retval, io, sizeof (MojoInput)); retval->opaque = newopaque; } // if #endif return retval; } // MojoInput_tar_duplicate static void MojoInput_tar_close(MojoInput *io) { TARinput *input = (TARinput *) io->opaque; TARinfo *info = (TARinfo *) input->ar->opaque; //input->ar->io->close(input->ar->io); info->input = NULL; free(input); free(io); } // MojoInput_tar_close // MojoArchive implementation... static boolean MojoArchive_tar_enumerate(MojoArchive *ar) { TARinfo *info = (TARinfo *) ar->opaque; MojoArchive_resetEntry(&ar->prevEnum); if (info->input != NULL) fatal("BUG: tar entry still open on new enumeration"); info->curFileStart = info->nextEnumPos = 0; return true; } // MojoArchive_tar_enumerate // These are byte offsets where fields start in the tar header blocks. #define TAR_FNAME 0 #define TAR_FNAMELEN 100 #define TAR_MODE 100 #define TAR_MODELEN 8 #define TAR_UID 108 #define TAR_UIDLEN 8 #define TAR_GID 116 #define TAR_GIDLEN 8 #define TAR_SIZE 124 #define TAR_SIZELEN 12 #define TAR_MTIME 136 #define TAR_MTIMELEN 12 #define TAR_CHKSUM 148 #define TAR_CHKSUMLEN 8 #define TAR_TYPE 156 #define TAR_TYPELEN 1 #define TAR_LINKNAME 157 #define TAR_LINKNAMELEN 100 #define TAR_MAGIC 257 #define TAR_MAGICLEN 6 #define TAR_VERSION 263 #define TAR_VERSIONLEN 2 #define TAR_UNAME 265 #define TAR_UNAMELEN 32 #define TAR_GNAME 297 #define TAR_GNAMELEN 32 #define TAR_DEVMAJOR 329 #define TAR_DEVMAJORLEN 8 #define TAR_DEVMINOR 337 #define TAR_DEVMINORLEN 8 #define TAR_FNAMEPRE 345 #define TAR_FNAMEPRELEN 155 // tar entry types... #define TAR_TYPE_FILE '0' #define TAR_TYPE_HARDLINK '1' #define TAR_TYPE_SYMLINK '2' #define TAR_TYPE_CHARDEV '3' #define TAR_TYPE_BLOCKDEV '4' #define TAR_TYPE_DIRECTORY '5' #define TAR_TYPE_FIFO '6' static boolean is_ustar(const uint8 *block) { return ( (memcmp(&block[TAR_MAGIC], "ustar ", TAR_MAGICLEN) == 0) || (memcmp(&block[TAR_MAGIC], "ustar\0", TAR_MAGICLEN) == 0) ); } // is_ustar static int64 octal_convert(const uint8 *str, const size_t len) { int64 retval = 0; int64 multiplier = 1; const uint8 *end = str + len; const uint8 *ptr; while ((*str == ' ') && (str != end)) str++; ptr = str; while ((ptr != end) && (*ptr >= '0') && (*ptr <= '7')) ptr++; while (--ptr >= str) { uint64 val = *ptr - '0'; retval += val * multiplier; multiplier *= 8; } // while return retval; } // octal_convert static const MojoArchiveEntry *MojoArchive_tar_enumNext(MojoArchive *ar) { TARinfo *info = (TARinfo *) ar->opaque; boolean zeroes = true; boolean ustar = false; uint8 scratch[512]; uint8 block[512]; size_t fnamelen = 0; int type = 0; memset(scratch, '\0', sizeof (scratch)); MojoArchive_resetEntry(&ar->prevEnum); if (info->input != NULL) fatal("BUG: tar entry still open on new enumeration"); if (!ar->io->seek(ar->io, info->nextEnumPos)) return NULL; // Find a non-zero block of data. Tarballs have two 512 blocks filled with // null bytes at the end of the archive, but you can cat tarballs // together, so you can't treat them as EOF indicators. Just skip them. while (zeroes) { if (ar->io->read(ar->io, block, sizeof (block)) != sizeof (block)) return NULL; // !!! FIXME: fatal() ? zeroes = (memcmp(block, scratch, sizeof (block)) == 0); } // while // !!! FIXME We should probably check the checksum. ustar = is_ustar(block); ar->prevEnum.perms = (uint16) octal_convert(&block[TAR_MODE], TAR_MODELEN); ar->prevEnum.filesize = octal_convert(&block[TAR_SIZE], TAR_SIZELEN); info->curFileStart = info->nextEnumPos + 512; info->nextEnumPos += 512 + ar->prevEnum.filesize; if (ar->prevEnum.filesize % 512) info->nextEnumPos += 512 - (ar->prevEnum.filesize % 512); // We count on (scratch) being zeroed out here! // prefix of filename is at the end for legacy compat. if (ustar) memcpy(scratch, &block[TAR_FNAMEPRE], TAR_FNAMEPRELEN); fnamelen = strlen((const char *) scratch); memcpy(&scratch[fnamelen], &block[TAR_FNAME], TAR_FNAMELEN); fnamelen += strlen((const char *) &scratch[fnamelen]); if (fnamelen == 0) return NULL; // corrupt file. !!! FIXME: fatal() ? ar->prevEnum.filename = xstrdup((const char *) scratch); type = block[TAR_TYPE]; if (type == 0) // some archivers do the file type as 0 instead of '0'. type = TAR_TYPE_FILE; if (ar->prevEnum.filename[fnamelen-1] == '/') { while (ar->prevEnum.filename[fnamelen-1] == '/') ar->prevEnum.filename[--fnamelen] = '\0'; // legacy tar entries don't have a dir type, they just append a '/' to // the filename... if ((!ustar) && (type == TAR_TYPE_FILE)) type = TAR_TYPE_DIRECTORY; } // if ar->prevEnum.type = MOJOARCHIVE_ENTRY_UNKNOWN; if (type == TAR_TYPE_FILE) ar->prevEnum.type = MOJOARCHIVE_ENTRY_FILE; else if (type == TAR_TYPE_DIRECTORY) ar->prevEnum.type = MOJOARCHIVE_ENTRY_DIR; else if (type == TAR_TYPE_SYMLINK) { ar->prevEnum.type = MOJOARCHIVE_ENTRY_SYMLINK; memcpy(scratch, &block[TAR_LINKNAME], TAR_LINKNAMELEN); scratch[TAR_LINKNAMELEN] = '\0'; // just in case. ar->prevEnum.linkdest = xstrdup((const char *) scratch); } // else if return &ar->prevEnum; } // MojoArchive_tar_enumNext static MojoInput *MojoArchive_tar_openCurrentEntry(MojoArchive *ar) { TARinfo *info = (TARinfo *) ar->opaque; MojoInput *io = NULL; TARinput *opaque = NULL; if (info->curFileStart == 0) return NULL; // Can't open multiple, since we would end up decompressing twice // to enumerate the next file, so I imposed this limitation for now. if (info->input != NULL) fatal("BUG: tar entry double open"); opaque = (TARinput *) xmalloc(sizeof (TARinput)); opaque->ar = ar; opaque->fsize = ar->prevEnum.filesize; opaque->offset = info->curFileStart; io = (MojoInput *) xmalloc(sizeof (MojoInput)); io->ready = MojoInput_tar_ready; io->read = MojoInput_tar_read; io->seek = MojoInput_tar_seek; io->tell = MojoInput_tar_tell; io->length = MojoInput_tar_length; io->duplicate = MojoInput_tar_duplicate; io->close = MojoInput_tar_close; io->opaque = opaque; info->input = io; return io; } // MojoArchive_tar_openCurrentEntry static void MojoArchive_tar_close(MojoArchive *ar) { TARinfo *info = (TARinfo *) ar->opaque; MojoArchive_resetEntry(&ar->prevEnum); ar->io->close(ar->io); free(info); free(ar); } // MojoArchive_tar_close MojoArchive *MojoArchive_createTAR(MojoInput *io) { MojoArchive *ar = NULL; uint8 sig[512]; const int64 br = io->read(io, sig, sizeof (sig)); // See if this is a tar archive. We only support "USTAR" format, // since it has a detectable header. GNU and BSD tar has been creating // these for years, so it's okay to ignore other ones, I guess. if ((!io->seek(io, 0)) || (br != sizeof (sig)) || (!is_ustar(sig)) ) return NULL; // okay, it's a tarball, we're good to go. ar = (MojoArchive *) xmalloc(sizeof (MojoArchive)); ar->opaque = (TARinfo *) xmalloc(sizeof (TARinfo)); ar->enumerate = MojoArchive_tar_enumerate; ar->enumNext = MojoArchive_tar_enumNext; ar->openCurrentEntry = MojoArchive_tar_openCurrentEntry; ar->close = MojoArchive_tar_close; ar->io = io; return ar; } // MojoArchive_createTAR #endif // SUPPORT_TAR // end of archive_tar.c ...