/** * 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. * Copyright (c) 2006-2010 Ryan C. Gordon and others. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. 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"); // !!! FIXME: replace this with MojoInput_newFromSubset()? 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 ...