/** * 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 * */ #include "fileio.h" #include "platform.h" typedef MojoArchive* (*MojoArchiveCreateEntryPoint)(MojoInput *io); MojoArchive *MojoArchive_createZIP(MojoInput *io); MojoArchive *MojoArchive_createTAR(MojoInput *io); MojoArchive *MojoArchive_createUZ2(MojoInput *io); MojoArchive *MojoArchive_createPCK(MojoInput *io); MojoArchive *MojoArchive_createPKG(MojoInput *io); typedef struct { const char *ext; MojoArchiveCreateEntryPoint create; boolean hasMagic; // can determine file type from contents? } MojoArchiveType; // !!! FIXME: order by archiver, not extension, since most of this is // !!! FIXME: duplicates for .tar static const MojoArchiveType archives[] = { { "zip", MojoArchive_createZIP, true }, { "tar", MojoArchive_createTAR, true }, { "tar.gz", MojoArchive_createTAR, true }, { "tar.xz", MojoArchive_createTAR, true }, { "tar.bz2", MojoArchive_createTAR, true }, { "tgz", MojoArchive_createTAR, true }, { "tbz2", MojoArchive_createTAR, true }, { "tb2", MojoArchive_createTAR, true }, { "tbz", MojoArchive_createTAR, true }, { "txz", MojoArchive_createTAR, true }, { "uz2", MojoArchive_createUZ2, false }, { "pck", MojoArchive_createPCK, true }, { "pkg", MojoArchive_createPKG, true }, }; #if SUPPORT_GZIP #include "miniz.h" #define GZIP_READBUFSIZE (128 * 1024) static MojoInput *make_gzip_input(MojoInput *origio); typedef struct GZIPinfo { MojoInput *origio; uint64 uncompressed_position; uint8 buffer[GZIP_READBUFSIZE]; z_stream stream; } GZIPinfo; static voidpf mojoZlibAlloc(voidpf opaque, uInt items, uInt size) { return xmalloc(items * size); } // mojoZlibAlloc static void mojoZlibFree(voidpf opaque, voidpf address) { free(address); } // mojoZlibFree static void initializeZStream(z_stream *pstr) { memset(pstr, '\0', sizeof (z_stream)); pstr->zalloc = mojoZlibAlloc; pstr->zfree = mojoZlibFree; } // initializeZStream static boolean MojoInput_gzip_ready(MojoInput *io) { return true; // !!! FIXME: ready if there are bytes uncompressed. } // MojoInput_gzip_ready static boolean MojoInput_gzip_seek(MojoInput *io, uint64 offset) { // This is all really expensive. GZIPinfo *info = (GZIPinfo *) io->opaque; /* * If seeking backwards, we need to redecode the file * from the start and throw away the compressed bits until we hit * the offset we need. If seeking forward, we still need to * decode, but we don't rewind first. */ if (offset < info->uncompressed_position) { if (!info->origio->seek(info->origio, 0)) return false; inflateEnd(&info->stream); initializeZStream(&info->stream); if (inflateInit2(&info->stream, 31) != Z_OK) return false; info->uncompressed_position = 0; } // if while (info->uncompressed_position != offset) { uint8 buf[512]; uint32 maxread; int64 br; maxread = (uint32) (offset - info->uncompressed_position); if (maxread > sizeof (buf)) maxread = sizeof (buf); br = io->read(io, buf, maxread); if (br != maxread) return false; } /* while */ return true; } // MojoInput_gzip_seek static int64 MojoInput_gzip_tell(MojoInput *io) { return (((GZIPinfo *) io->opaque)->uncompressed_position); } // MojoInput_gzip_tell static int64 MojoInput_gzip_length(MojoInput *io) { return -1; } // MojoInput_gzip_length static int64 MojoInput_gzip_read(MojoInput *io, void *buf, uint32 bufsize) { GZIPinfo *info = (GZIPinfo *) io->opaque; MojoInput *origio = info->origio; int64 retval = 0; if (bufsize == 0) return 0; // quick rejection. info->stream.next_out = buf; info->stream.avail_out = bufsize; while (retval < ((int64) bufsize)) { const uint32 before = info->stream.total_out; int rc; if (info->stream.avail_in == 0) { int64 br = origio->length(origio) - origio->tell(origio); if (br > 0) { if (br > GZIP_READBUFSIZE) br = GZIP_READBUFSIZE; br = origio->read(origio, info->buffer, (uint32) br); if (br <= 0) return -1; info->stream.next_in = info->buffer; info->stream.avail_in = (uint32) br; } // if } // if rc = inflate(&info->stream, Z_SYNC_FLUSH); retval += (info->stream.total_out - before); if ((rc == Z_STREAM_END) && (retval == 0)) return 0; else if ((rc != Z_OK) && (rc != Z_STREAM_END)) return -1; } // while assert(retval >= 0); info->uncompressed_position += (uint32) retval; return retval; } // MojoInput_gzip_read static MojoInput* MojoInput_gzip_duplicate(MojoInput *io) { GZIPinfo *info = (GZIPinfo *) io->opaque; MojoInput *retval = NULL; MojoInput *newio = info->origio->duplicate(info->origio); if (newio != NULL) { retval = make_gzip_input(newio); if (retval != NULL) retval->seek(retval, io->tell(io)); // slow, slow, slow... } // if return retval; } // MojoInput_gzip_duplicate static void MojoInput_gzip_close(MojoInput *io) { GZIPinfo *info = (GZIPinfo *) io->opaque; if (info->origio != NULL) info->origio->close(info->origio); inflateEnd(&info->stream); free(info); free(io); } // MojoInput_gzip_close static MojoInput *make_gzip_input(MojoInput *origio) { MojoInput *io = NULL; GZIPinfo *info = (GZIPinfo *) xmalloc(sizeof (GZIPinfo)); initializeZStream(&info->stream); if (inflateInit2(&info->stream, 31) != Z_OK) { free(info); return NULL; } // if info->origio = origio; io = (MojoInput *) xmalloc(sizeof (MojoInput)); io->ready = MojoInput_gzip_ready; io->read = MojoInput_gzip_read; io->seek = MojoInput_gzip_seek; io->tell = MojoInput_gzip_tell; io->length = MojoInput_gzip_length; io->duplicate = MojoInput_gzip_duplicate; io->close = MojoInput_gzip_close; io->opaque = info; return io; } // make_gzip_input #endif // SUPPORT_GZIP #if SUPPORT_BZIP2 #include "bzip2/bzlib.h" #define BZIP2_READBUFSIZE (128 * 1024) static MojoInput *make_bzip2_input(MojoInput *origio); typedef struct BZIP2info { MojoInput *origio; uint64 uncompressed_position; uint8 buffer[BZIP2_READBUFSIZE]; bz_stream stream; } BZIP2info; static void *mojoBzlib2Alloc(void *opaque, int items, int size) { return xmalloc(items * size); } // mojoBzlib2Alloc static void mojoBzlib2Free(void *opaque, void *address) { free(address); } // mojoBzlib2Free static void initializeBZ2Stream(bz_stream *pstr) { memset(pstr, '\0', sizeof (bz_stream)); pstr->bzalloc = mojoBzlib2Alloc; pstr->bzfree = mojoBzlib2Free; } // initializeBZ2Stream static boolean MojoInput_bzip2_ready(MojoInput *io) { return true; // !!! FIXME: ready if there are bytes uncompressed. } // MojoInput_bzip2_ready static boolean MojoInput_bzip2_seek(MojoInput *io, uint64 offset) { // This is all really expensive. BZIP2info *info = (BZIP2info *) io->opaque; /* * If seeking backwards, we need to redecode the file * from the start and throw away the compressed bits until we hit * the offset we need. If seeking forward, we still need to * decode, but we don't rewind first. */ if (offset < info->uncompressed_position) { #if 0 /* we do a copy so state is sane if inflateInit2() fails. */ bz_stream str; initializeBZ2Stream(&str); if (BZ2_bzDecompressInit(&str, 0, 0) != BZ_OK) return false; if (!info->origio->seek(info->origio, 0)) return false; // !!! FIXME: leaking (str)? BZ2_bzDecompressEnd(&info->stream); memcpy(&info->stream, &str, sizeof (bz_stream)); #endif if (!info->origio->seek(info->origio, 0)) return false; BZ2_bzDecompressEnd(&info->stream); initializeBZ2Stream(&info->stream); if (BZ2_bzDecompressInit(&info->stream, 0, 0) != BZ_OK) return false; info->uncompressed_position = 0; } // if while (info->uncompressed_position != offset) { uint8 buf[512]; uint32 maxread; int64 br; maxread = (uint32) (offset - info->uncompressed_position); if (maxread > sizeof (buf)) maxread = sizeof (buf); br = io->read(io, buf, maxread); if (br != maxread) return false; } /* while */ return true; } // MojoInput_bzip2_seek static int64 MojoInput_bzip2_tell(MojoInput *io) { return (((BZIP2info *) io->opaque)->uncompressed_position); } // MojoInput_bzip2_tell static int64 MojoInput_bzip2_length(MojoInput *io) { return -1; } // MojoInput_bzip2_length static int64 MojoInput_bzip2_read(MojoInput *io, void *buf, uint32 bufsize) { BZIP2info *info = (BZIP2info *) io->opaque; MojoInput *origio = info->origio; int64 retval = 0; if (bufsize == 0) return 0; // quick rejection. info->stream.next_out = buf; info->stream.avail_out = bufsize; while (retval < ((int64) bufsize)) { const uint32 before = info->stream.total_out_lo32; int rc; if (info->stream.avail_in == 0) { int64 br = origio->length(origio) - origio->tell(origio); if (br > 0) { if (br > BZIP2_READBUFSIZE) br = BZIP2_READBUFSIZE; br = origio->read(origio, info->buffer, (uint32) br); if (br <= 0) return -1; info->stream.next_in = (char *) info->buffer; info->stream.avail_in = (uint32) br; } // if } // if rc = BZ2_bzDecompress(&info->stream); retval += (info->stream.total_out_lo32 - before); if (rc != BZ_OK) return -1; } // while assert(retval >= 0); info->uncompressed_position += (uint32) retval; return retval; } // MojoInput_bzip2_read static MojoInput* MojoInput_bzip2_duplicate(MojoInput *io) { BZIP2info *info = (BZIP2info *) io->opaque; MojoInput *retval = NULL; MojoInput *newio = info->origio->duplicate(info->origio); if (newio != NULL) { retval = make_bzip2_input(newio); if (retval != NULL) retval->seek(retval, io->tell(io)); // slow, slow, slow... } // if return retval; } // MojoInput_bzip2_duplicate static void MojoInput_bzip2_close(MojoInput *io) { BZIP2info *info = (BZIP2info *) io->opaque; if (info->origio != NULL) info->origio->close(info->origio); BZ2_bzDecompressEnd(&info->stream); free(info); free(io); } // MojoInput_bzip2_close static MojoInput *make_bzip2_input(MojoInput *origio) { MojoInput *io = NULL; BZIP2info *info = (BZIP2info *) xmalloc(sizeof (BZIP2info)); initializeBZ2Stream(&info->stream); if (BZ2_bzDecompressInit(&info->stream, 0, 0) != BZ_OK) { free(info); return NULL; } // if info->origio = origio; io = (MojoInput *) xmalloc(sizeof (MojoInput)); io->ready = MojoInput_bzip2_ready; io->read = MojoInput_bzip2_read; io->seek = MojoInput_bzip2_seek; io->tell = MojoInput_bzip2_tell; io->length = MojoInput_bzip2_length; io->duplicate = MojoInput_bzip2_duplicate; io->close = MojoInput_bzip2_close; io->opaque = info; return io; } // make_bzip2_input #endif // SUPPORT_BZIP2 #if SUPPORT_XZ #include "lzma.h" #define XZ_READBUFSIZE (128 * 1024) static MojoInput *make_xz_input(MojoInput *origio); typedef struct XZinfo { MojoInput *origio; uint64 uncompressed_position; uint8 buffer[XZ_READBUFSIZE]; lzma_stream stream; } XZinfo; static void *mojoLzmaAlloc(void *opaque, size_t items, size_t size) { return xmalloc(items * size); } // mojoLzmaAlloc static void mojoLzmaFree(void *opaque, void *address) { free(address); } // mojoZlibFree static void initializeXZStream(lzma_stream *pstr) { static lzma_allocator lzmaAlloc = { mojoLzmaAlloc, mojoLzmaFree }; memset(pstr, '\0', sizeof (lzma_stream)); pstr->allocator = &lzmaAlloc; } // initializeXZStream static boolean MojoInput_xz_ready(MojoInput *io) { return true; // !!! FIXME: ready if there are bytes uncompressed. } // MojoInput_xz_ready static boolean MojoInput_xz_seek(MojoInput *io, uint64 offset) { // This is all really expensive. XZinfo *info = (XZinfo *) io->opaque; /* * If seeking backwards, we need to redecode the file * from the start and throw away the compressed bits until we hit * the offset we need. If seeking forward, we still need to * decode, but we don't rewind first. */ if (offset < info->uncompressed_position) { lzma_stream *strm = &info->stream; if (!info->origio->seek(info->origio, 0)) return false; lzma_end(strm); initializeXZStream(strm); if (lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED) != LZMA_OK) return false; info->uncompressed_position = 0; } // if while (info->uncompressed_position != offset) { uint8 buf[512]; uint32 maxread; int64 br; maxread = (uint32) (offset - info->uncompressed_position); if (maxread > sizeof (buf)) maxread = sizeof (buf); br = io->read(io, buf, maxread); if (br != maxread) return false; } /* while */ return true; } // MojoInput_xz_seek static int64 MojoInput_xz_tell(MojoInput *io) { return (((XZinfo *) io->opaque)->uncompressed_position); } // MojoInput_xz_tell static int64 MojoInput_xz_length(MojoInput *io) { return -1; } // MojoInput_xz_length static int64 MojoInput_xz_read(MojoInput *io, void *buf, uint32 bufsize) { XZinfo *info = (XZinfo *) io->opaque; MojoInput *origio = info->origio; int64 retval = 0; if (bufsize == 0) return 0; // quick rejection. info->stream.next_out = buf; info->stream.avail_out = bufsize; while (retval < ((int64) bufsize)) { const uint32 before = info->stream.total_out; lzma_ret rc; if (info->stream.avail_in == 0) { int64 br = origio->length(origio) - origio->tell(origio); if (br > 0) { if (br > XZ_READBUFSIZE) br = XZ_READBUFSIZE; br = origio->read(origio, info->buffer, (uint32) br); if (br <= 0) return -1; info->stream.next_in = info->buffer; info->stream.avail_in = (uint32) br; } // if } // if rc = lzma_code(&info->stream, LZMA_RUN); retval += (info->stream.total_out - before); if (rc != LZMA_OK) return -1; } // while assert(retval >= 0); info->uncompressed_position += (uint32) retval; return retval; } // MojoInput_xz_read static MojoInput* MojoInput_xz_duplicate(MojoInput *io) { XZinfo *info = (XZinfo *) io->opaque; MojoInput *retval = NULL; MojoInput *newio = info->origio->duplicate(info->origio); if (newio != NULL) { retval = make_xz_input(newio); if (retval != NULL) retval->seek(retval, io->tell(io)); // slow, slow, slow... } // if return retval; } // MojoInput_xz_duplicate static void MojoInput_xz_close(MojoInput *io) { XZinfo *info = (XZinfo *) io->opaque; if (info->origio != NULL) info->origio->close(info->origio); lzma_end(&info->stream); free(info); free(io); } // MojoInput_xz_close static MojoInput *make_xz_input(MojoInput *origio) { MojoInput *io = NULL; XZinfo *info = (XZinfo *) xmalloc(sizeof (XZinfo)); lzma_stream *strm = &info->stream; // UINT64_MAX is the memory usage limit (so basically, no artificial // limit here). Internally, this holds its entire dictionary in // RAM (8 megabytes by default), plus a few other structures, so we // shouldn't eat tons of memory in the normal case. Note that the // dictionary can max out at _four gigabytes_, but obviously no one does // that. initializeXZStream(strm); if (lzma_stream_decoder(strm, UINT64_MAX, LZMA_CONCATENATED) != LZMA_OK) { free(info); return NULL; } // if info->origio = origio; io = (MojoInput *) xmalloc(sizeof (MojoInput)); io->ready = MojoInput_xz_ready; io->read = MojoInput_xz_read; io->seek = MojoInput_xz_seek; io->tell = MojoInput_xz_tell; io->length = MojoInput_xz_length; io->duplicate = MojoInput_xz_duplicate; io->close = MojoInput_xz_close; io->opaque = info; return io; } // make_xz_input #endif // SUPPORT_XZ MojoInput *MojoInput_newCompressedStream(MojoInput *origio) { #if SUPPORT_GZIP || SUPPORT_BZIP2 || SUPPORT_XZ // Look at the first piece of the file to decide if it is compressed // by a general compression algorithm, and if so, wrap the MojoInput // in a decompressor. uint8 magic[6]; const int64 br = origio->read(origio, magic, sizeof (magic)); if ((origio->seek(origio, 0)) && (br == sizeof (magic))) { #if SUPPORT_GZIP { static const uint8 gzip_sig[] = { 0x1F, 0x8B, 0x08 }; if (memcmp(magic, gzip_sig, sizeof (gzip_sig)) == 0) return make_gzip_input(origio); } #endif #if SUPPORT_BZIP2 { static const uint8 bzip2_sig[] = { 0x42, 0x5A }; if (memcmp(magic, bzip2_sig, sizeof (bzip2_sig)) == 0) return make_bzip2_input(origio); } #endif #if SUPPORT_XZ { static const uint8 xz_sig[] = { 0xFD, '7', 'z', 'X', 'Z', 0x00 }; if (memcmp(magic, xz_sig, sizeof (xz_sig)) == 0) return make_xz_input(origio); } #endif } // if #endif return NULL; } // MojoInput_newCompressedStream MojoArchive *MojoArchive_newFromInput(MojoInput *_io, const char *origfname) { int i; MojoArchive *retval = NULL; const char *ext = NULL; MojoInput *io = MojoInput_newCompressedStream(_io); if (io == NULL) io = _io; if (origfname != NULL) { ext = strrchr(origfname, '/'); if (ext == NULL) ext = strchr(origfname, '.'); else ext = strchr(ext+1, '.'); } // if while (ext != NULL) { // Try for an exact match by filename extension. ext++; // skip that '.' for (i = 0; i < STATICARRAYLEN(archives); i++) { const MojoArchiveType *arc = &archives[i]; if (strcasecmp(ext, arc->ext) == 0) return arc->create(io); } // for ext = strchr(ext, '.'); } // while // Try any that could be determined without the file extension... for (i = 0; i < STATICARRAYLEN(archives); i++) { const MojoArchiveType *arc = &archives[i]; if ((arc->hasMagic) && ((retval = arc->create(io)) != NULL)) return retval; } // for io->close(io); return NULL; // nothing can handle this data. } // MojoArchive_newFromInput void MojoArchive_resetEntry(MojoArchiveEntry *info) { free(info->filename); free(info->linkdest); memset(info, '\0', sizeof (MojoArchiveEntry)); } // MojoArchive_resetEntry // !!! FIXME: I'd rather not use a callback here, but I can't see a cleaner // !!! FIXME: way right now... boolean MojoInput_toPhysicalFile(MojoInput *in, const char *fname, uint16 perms, MojoChecksums *checksums, int64 maxbytes, MojoInput_FileCopyCallback cb, void *data) { boolean retval = false; uint32 start = MojoPlatform_ticks(); void *out = NULL; boolean iofailure = false; int64 flen = 0; int64 bw = 0; MojoChecksumContext sumctx; if (in == NULL) return false; if (checksums != NULL) { memset(checksums, '\0', sizeof (MojoChecksums)); MojoChecksum_init(&sumctx); } // if // Wait for a ready(), so length() can be meaningful on network streams. while ((!in->ready(in)) && (!iofailure)) { MojoPlatform_sleep(100); if (cb != NULL) { if (!cb(MojoPlatform_ticks() - start, 0, 0, -1, data)) iofailure = true; } // if } // while flen = in->length(in); if ((maxbytes >= 0) && (flen > maxbytes)) flen = maxbytes; MojoPlatform_unlink(fname); if (!iofailure) { const uint32 flags = MOJOFILE_WRITE|MOJOFILE_CREATE|MOJOFILE_TRUNCATE; const uint16 mode = MojoPlatform_defaultFilePerms(); out = MojoPlatform_open(fname, flags, mode); } // if if (out != NULL) { while (!iofailure) { int64 br = 0; int64 maxread = sizeof (scratchbuf_128k); // see if we need to clamp to eof or maxbytes... if (flen >= 0) { const int64 avail = flen - bw; if (avail < maxread) { maxread = avail; if (maxread == 0) break; // nothing left to do, break out. } // if } // if // If there's a callback, then poll. Otherwise, just block on // the reads from the MojoInput. if ((cb != NULL) && (!in->ready(in))) MojoPlatform_sleep(100); else { br = in->read(in, scratchbuf_128k, (uint32) maxread); if (br == 0) // we're done! break; else if (br < 0) iofailure = true; else { if (MojoPlatform_write(out, scratchbuf_128k, (uint32) br) != br) iofailure = true; else { if (checksums != NULL) MojoChecksum_append(&sumctx, scratchbuf_128k, (uint32) br); bw += br; } // else } // else } // else if (cb != NULL) { if (!cb(MojoPlatform_ticks() - start, br, bw, flen, data)) iofailure = true; } // if } // while if (MojoPlatform_close(out) != 0) iofailure = true; else if (bw != flen) iofailure = true; if (iofailure) MojoPlatform_unlink(fname); else { MojoPlatform_chmod(fname, perms); if (checksums != NULL) MojoChecksum_finish(&sumctx, checksums); retval = true; } // else } // if in->close(in); return retval; } // MojoInput_toPhysicalFile MojoInput *MojoInput_newFromArchivePath(MojoArchive *ar, const char *fname) { MojoInput *retval = NULL; if (ar->enumerate(ar)) { const MojoArchiveEntry *entinfo; while ((entinfo = ar->enumNext(ar)) != NULL) { if (strcmp(entinfo->filename, fname) == 0) { if (entinfo->type == MOJOARCHIVE_ENTRY_FILE) retval = ar->openCurrentEntry(ar); break; } // if } // while } // if return retval; } // MojoInput_newFromArchivePath // MojoInputs from files on the OS filesystem. typedef struct { void *handle; char *path; } MojoInputFileInstance; static boolean MojoInput_file_ready(MojoInput *io) { // !!! FIXME: select()? Does that help with network filesystems? return true; } // MojoInput_file_ready static int64 MojoInput_file_read(MojoInput *io, void *buf, uint32 bufsize) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; return MojoPlatform_read(inst->handle, buf, bufsize); } // MojoInput_file_read static boolean MojoInput_file_seek(MojoInput *io, uint64 pos) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; return (MojoPlatform_seek(inst->handle, pos, MOJOSEEK_SET) == pos); } // MojoInput_file_seek static int64 MojoInput_file_tell(MojoInput *io) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; return MojoPlatform_tell(inst->handle); } // MojoInput_file_tell static int64 MojoInput_file_length(MojoInput *io) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; return MojoPlatform_flen(inst->handle); } // MojoInput_file_length static MojoInput *MojoInput_file_duplicate(MojoInput *io) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; return MojoInput_newFromFile(inst->path); } // MojoInput_file_duplicate static void MojoInput_file_close(MojoInput *io) { MojoInputFileInstance *inst = (MojoInputFileInstance *) io->opaque; MojoPlatform_close(inst->handle); free(inst->path); free(inst); free(io); } // MojoInput_file_close MojoInput *MojoInput_newFromFile(const char *path) { MojoInput *io = NULL; void *f = NULL; f = MojoPlatform_open(path, MOJOFILE_READ, 0); if (f != NULL) { MojoInputFileInstance *inst; inst = (MojoInputFileInstance *) xmalloc(sizeof (MojoInputFileInstance)); inst->path = xstrdup(path); inst->handle = f; io = (MojoInput *) xmalloc(sizeof (MojoInput)); io->ready = MojoInput_file_ready; io->read = MojoInput_file_read; io->seek = MojoInput_file_seek; io->tell = MojoInput_file_tell; io->length = MojoInput_file_length; io->duplicate = MojoInput_file_duplicate; io->close = MojoInput_file_close; io->opaque = inst; } // if return io; } // MojoInput_newFromFile // MojoInputs from blocks of memory. typedef struct { void *ptr; // original pointer from xmalloc() uint32 *refcount; // address in xmalloc()'d block for reference count. const uint8 *data; // base of actual "file" data in xmalloc()'d block. uint32 len; // size, in bytes, of "file" data. uint32 pos; // current read position. } MojoInputMemInstance; static boolean MojoInput_memory_ready(MojoInput *io) { return true; // always ready! } // MojoInput_memory_ready static int64 MojoInput_memory_read(MojoInput *io, void *buf, uint32 bufsize) { MojoInputMemInstance *inst = (MojoInputMemInstance *) io->opaque; const uint32 avail = inst->len - inst->pos; if (bufsize > avail) bufsize = avail; memcpy(buf, inst->data + inst->pos, bufsize); inst->pos += bufsize; return bufsize; } // MojoInput_memory_read static boolean MojoInput_memory_seek(MojoInput *io, uint64 pos) { MojoInputMemInstance *inst = (MojoInputMemInstance *) io->opaque; if (pos > (uint64) inst->len) return false; inst->pos = (uint32) pos; return true; } // MojoInput_memory_seek static int64 MojoInput_memory_tell(MojoInput *io) { MojoInputMemInstance *inst = (MojoInputMemInstance *) io->opaque; return (int64) inst->pos; } // MojoInput_memory_tell static int64 MojoInput_memory_length(MojoInput *io) { MojoInputMemInstance *inst = (MojoInputMemInstance *) io->opaque; return (int64) inst->len; } // MojoInput_memory_length static MojoInput *MojoInput_memory_duplicate(MojoInput *io) { MojoInputMemInstance *srcinst = (MojoInputMemInstance *) io->opaque; MojoInput *retval = NULL; MojoInputMemInstance *inst = NULL; if (srcinst->refcount != NULL) { // we don't copy the data for each duplicate; we just bump a reference // counter. We free the data when all referencers are closed. (*srcinst->refcount)++; // !!! FIXME: not thread safe! } // if inst = (MojoInputMemInstance*) xmalloc(sizeof (MojoInputMemInstance)); memcpy(inst, srcinst, sizeof (MojoInputMemInstance)); inst->pos = 0; retval = (MojoInput *) xmalloc(sizeof (MojoInput)); memcpy(retval, io, sizeof (MojoInput)); retval->opaque = inst; return retval; } // MojoInput_memory_duplicate static void MojoInput_memory_close(MojoInput *io) { MojoInputMemInstance *inst = (MojoInputMemInstance *) io->opaque; if (inst->refcount != NULL) // memory we have to free? { assert(*inst->refcount > 0); if (--(*inst->refcount) == 0) // !!! FIXME: not thread safe! free(inst->ptr); } // if free(inst); free(io); } // MojoInput_memory_close MojoInput *MojoInput_newFromMemory(const uint8 *ptr, uint32 len, int constant) { MojoInput *io = (MojoInput *) xmalloc(sizeof (MojoInput)); MojoInputMemInstance *inst = (MojoInputMemInstance*) xmalloc(sizeof (MojoInputMemInstance)); if (constant) inst->data = ptr; else { inst->ptr = xmalloc(len + sizeof (uint32)); inst->refcount = (uint32 *) inst->ptr; inst->data = ((const uint8 *) inst->ptr) + sizeof (uint32); *inst->refcount = 1; memcpy((void *) inst->data, ptr, len); } // else inst->len = len; io->ready = MojoInput_memory_ready; io->read = MojoInput_memory_read; io->seek = MojoInput_memory_seek; io->tell = MojoInput_memory_tell; io->length = MojoInput_memory_length; io->duplicate = MojoInput_memory_duplicate; io->close = MojoInput_memory_close; io->opaque = inst; return io; } // MojoInput_newFromMemory // Put a limit on the range of an existing MojoInput. typedef struct { MojoInput *io; // original io we're a subset of. uint64 pos; uint64 start; uint64 end; } MojoInputSubsetInstance; static boolean MojoInput_subset_ready(MojoInput *io) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; return inst->io->ready(inst->io); } // MojoInput_subset_ready static int64 MojoInput_subset_read(MojoInput *io, void *buf, uint32 bufsize) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; const uint32 avail = inst->end - inst->pos; int64 rc; assert(inst->pos < inst->end); if (bufsize > avail) bufsize = avail; rc = inst->io->read(inst->io, buf, bufsize); if (rc > 0) inst->pos += rc; return rc; } // MojoInput_subset_read static boolean MojoInput_subset_seek(MojoInput *io, uint64 pos) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; if (pos > (inst->end - inst->start)) return false; else if (!inst->io->seek(inst->io, pos + inst->start)) return false; inst->pos = pos; return true; } // MojoInput_subset_seek static int64 MojoInput_subset_tell(MojoInput *io) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; return (int64) inst->pos; } // MojoInput_subset_tell static int64 MojoInput_subset_length(MojoInput *io) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; return (int64) (inst->end - inst->start); } // MojoInput_subset_length static MojoInput *MojoInput_subset_duplicate(MojoInput *io) { MojoInputSubsetInstance *srcinst = (MojoInputSubsetInstance *) io->opaque; MojoInput *dupio = io->duplicate(io); MojoInput *retval = NULL; MojoInputSubsetInstance *inst = NULL; if (dupio == NULL) return NULL; if (!dupio->seek(dupio, 0)) { dupio->close(dupio); return NULL; } // if inst = (MojoInputSubsetInstance*) xmalloc(sizeof (MojoInputSubsetInstance)); memcpy(inst, srcinst, sizeof (MojoInputSubsetInstance)); inst->io = dupio; inst->pos = 0; retval = (MojoInput *) xmalloc(sizeof (MojoInput)); memcpy(retval, io, sizeof (MojoInput)); retval->opaque = inst; return retval; } // MojoInput_subset_duplicate static void MojoInput_subset_close(MojoInput *io) { MojoInputSubsetInstance *inst = (MojoInputSubsetInstance *) io->opaque; inst->io->close(inst->io); free(inst); free(io); } // MojoInput_subset_close MojoInput *MojoInput_newFromSubset(MojoInput *_io, const uint64 start, const uint64 end) { MojoInput *io; MojoInputSubsetInstance *inst; assert(end > start); if (!_io->seek(_io, start)) return NULL; io = (MojoInput *) xmalloc(sizeof (MojoInput)); inst = (MojoInputSubsetInstance*)xmalloc(sizeof (MojoInputSubsetInstance)); inst->io = _io; inst->pos = 0; inst->start = start; inst->end = end; io->ready = MojoInput_subset_ready; io->read = MojoInput_subset_read; io->seek = MojoInput_subset_seek; io->tell = MojoInput_subset_tell; io->length = MojoInput_subset_length; io->duplicate = MojoInput_subset_duplicate; io->close = MojoInput_subset_close; io->opaque = inst; return io; } // MojoInput_newFromSubset // MojoArchives from directories on the OS filesystem. typedef struct DirStack { void *dir; char *basepath; struct DirStack *next; } DirStack; static void pushDirStack(DirStack **_stack, const char *basepath, void *dir) { DirStack *stack = (DirStack *) xmalloc(sizeof (DirStack)); stack->dir = dir; stack->basepath = xstrdup(basepath); stack->next = *_stack; *_stack = stack; } // pushDirStack static void popDirStack(DirStack **_stack) { DirStack *stack = *_stack; if (stack != NULL) { DirStack *next = stack->next; if (stack->dir) MojoPlatform_closedir(stack->dir); free(stack->basepath); free(stack); *_stack = next; } // if } // popDirStack static void freeDirStack(DirStack **_stack) { while (*_stack) popDirStack(_stack); } // freeDirStack typedef struct { DirStack *dirs; char *base; } MojoArchiveDirInstance; static boolean MojoArchive_dir_enumerate(MojoArchive *ar) { MojoArchiveDirInstance *inst = (MojoArchiveDirInstance *) ar->opaque; void *dir = NULL; freeDirStack(&inst->dirs); MojoArchive_resetEntry(&ar->prevEnum); dir = MojoPlatform_opendir(inst->base); if (dir != NULL) pushDirStack(&inst->dirs, inst->base, dir); return (dir != NULL); } // MojoArchive_dir_enumerate static const MojoArchiveEntry *MojoArchive_dir_enumNext(MojoArchive *ar) { uint16 perms = 0644; //(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); char *fullpath = NULL; char *dent = NULL; // "dent" == "directory entry" MojoArchiveDirInstance *inst = (MojoArchiveDirInstance *) ar->opaque; const char *basepath; MojoArchive_resetEntry(&ar->prevEnum); if (inst->dirs == NULL) return NULL; basepath = inst->dirs->basepath; // if readdir fails, it's end of dir (!!! FIXME: what about i/o failures?) dent = MojoPlatform_readdir(inst->dirs->dir); if (dent == NULL) // end of dir? { popDirStack(&inst->dirs); return MojoArchive_dir_enumNext(ar); // try higher level in tree. } // if // MojoPlatform layer shouldn't return "." or ".." paths. assert((strcmp(dent, ".") != 0) && (strcmp(dent, "..") != 0)); fullpath = (char *) xmalloc(strlen(basepath) + strlen(dent) + 2); sprintf(fullpath, "%s/%s", basepath, dent); free(dent); ar->prevEnum.filename = xstrdup(fullpath + strlen(inst->base) + 1); ar->prevEnum.filesize = 0; ar->prevEnum.type = MOJOARCHIVE_ENTRY_UNKNOWN; // We currently force the perms from physical files, since CDs on // Linux tend to mark every files as executable and read-only. If you // want to install something with specific permissions, wrap it in a // tarball, or use Setup.File.permissions, or return a permissions // string from Setup.File.filter. //MojoPlatform_perms(fullpath, &perms); ar->prevEnum.perms = perms; if (MojoPlatform_isfile(fullpath)) { ar->prevEnum.type = MOJOARCHIVE_ENTRY_FILE; ar->prevEnum.filesize = MojoPlatform_filesize(fullpath); } // if else if (MojoPlatform_issymlink(fullpath)) { ar->prevEnum.type = MOJOARCHIVE_ENTRY_SYMLINK; ar->prevEnum.linkdest = MojoPlatform_readlink(fullpath); if (ar->prevEnum.linkdest == NULL) { free(fullpath); return MojoArchive_dir_enumNext(ar); } // if } // else if else if (MojoPlatform_isdir(fullpath)) { void *dir = MojoPlatform_opendir(fullpath); ar->prevEnum.type = MOJOARCHIVE_ENTRY_DIR; if (dir == NULL) { free(fullpath); return MojoArchive_dir_enumNext(ar); } // if // push this dir on the stack. Next enum will start there. pushDirStack(&inst->dirs, fullpath, dir); } // else if else { assert(false && "possible file i/o error?"); } // else free(fullpath); return &ar->prevEnum; } // MojoArchive_dir_enumNext static MojoInput *MojoArchive_dir_openCurrentEntry(MojoArchive *ar) { MojoInput *retval = NULL; MojoArchiveDirInstance *inst = (MojoArchiveDirInstance *) ar->opaque; if ((inst->dirs != NULL) && (ar->prevEnum.type == MOJOARCHIVE_ENTRY_FILE)) { char *fullpath = (char *) xmalloc(strlen(inst->base) + strlen(ar->prevEnum.filename) + 2); sprintf(fullpath, "%s/%s", inst->base, ar->prevEnum.filename); retval = MojoInput_newFromFile(fullpath); free(fullpath); } // if return retval; } // MojoArchive_dir_openCurrentEntry static void MojoArchive_dir_close(MojoArchive *ar) { MojoArchiveDirInstance *inst = (MojoArchiveDirInstance *) ar->opaque; freeDirStack(&inst->dirs); free(inst->base); free(inst); MojoArchive_resetEntry(&ar->prevEnum); free(ar); } // MojoArchive_dir_close MojoArchive *MojoArchive_newFromDirectory(const char *dirname) { MojoArchive *ar = NULL; MojoArchiveDirInstance *inst; char *real = MojoPlatform_realpath(dirname); if (real == NULL) return NULL; if (!MojoPlatform_exists(real, NULL)) return NULL; if (!MojoPlatform_isdir(real)) return NULL; inst = (MojoArchiveDirInstance *) xmalloc(sizeof (MojoArchiveDirInstance)); inst->base = real; ar = (MojoArchive *) xmalloc(sizeof (MojoArchive)); ar->enumerate = MojoArchive_dir_enumerate; ar->enumNext = MojoArchive_dir_enumNext; ar->openCurrentEntry = MojoArchive_dir_openCurrentEntry; ar->close = MojoArchive_dir_close; ar->offsetOfStart = -1; // doesn't mean anything here. ar->opaque = inst; return ar; } // MojoArchive_newFromDirectory boolean MojoInput_readui16(MojoInput *io, uint16 *ui16) { uint8 buf[sizeof (uint16)]; if (io->read(io, buf, sizeof (buf)) != sizeof (buf)) return false; *ui16 = ( (((uint16) buf[0]) << 0) | (((uint16) buf[1]) << 8) ); return true; } // MojoInput_readui16 boolean MojoInput_readui32(MojoInput *io, uint32 *ui32) { uint8 buf[sizeof (uint32)]; if (io->read(io, buf, sizeof (buf)) != sizeof (buf)) return false; *ui32 = ( (((uint32) buf[0]) << 0) | (((uint32) buf[1]) << 8) | (((uint32) buf[2]) << 16) | (((uint32) buf[3]) << 24) ); return true; } // MojoInput_readui32 boolean MojoInput_readui64(MojoInput *io, uint64 *ui64) { uint8 buf[sizeof (uint64)]; if (io->read(io, buf, sizeof (buf)) != sizeof (buf)) return false; *ui64 = ( (((uint64) buf[0]) << 0) | (((uint64) buf[1]) << 8) | (((uint64) buf[2]) << 16) | (((uint64) buf[3]) << 24) | (((uint64) buf[4]) << 32) | (((uint64) buf[5]) << 40) | (((uint64) buf[6]) << 48) | (((uint64) buf[7]) << 56) ); return true; } // MojoInput_readui64 MojoArchive *GBaseArchive = NULL; const char *GBaseArchivePath = NULL; MojoArchive *MojoArchive_initBaseArchive(void) { char *basepath = NULL; const char *cmd = NULL; MojoInput *io = NULL; if (GBaseArchive != NULL) return GBaseArchive; // already initialized. if ((cmd = cmdlinestr("base", "MOJOSETUP_BASE", NULL)) != NULL) { char *real = MojoPlatform_realpath(cmd); if (real != NULL) { if (MojoPlatform_isdir(real)) GBaseArchive = MojoArchive_newFromDirectory(real); else { io = MojoInput_newFromFile(real); if (io != NULL) GBaseArchive = MojoArchive_newFromInput(io, real); } // else if (GBaseArchive != NULL) basepath = real; else free(real); } // if } // else if else { basepath = MojoPlatform_appBinaryPath(); if (basepath != NULL) { io = MojoInput_newFromFile(basepath); if (io != NULL) { // See if there's a MOJOBASE signature at the end of the // file. This means we appended an archive to the executable, // for a self-extracting installer. This method works with // any archive type, even if it wasn't specifically designed // to be appended. uint8 buf[8]; uint64 size = 0; const int64 flen = io->length(io) - 16; if ( (flen > 0) && (io->seek(io, flen)) && (io->read(io, buf, 8) == 8) && (memcmp(buf, "MOJOBASE", 8) == 0) && (MojoInput_readui64(io, &size)) && (size < flen) ) { MojoInput *newio; newio = MojoInput_newFromSubset(io, flen - size, flen); if (newio != NULL) io = newio; } // if GBaseArchive = MojoArchive_newFromInput(io, basepath); } // if if (GBaseArchive == NULL) { // Just use the same directory as the binary instead. char *ptr = strrchr(basepath, '/'); if (ptr != NULL) *ptr = '\0'; else { free(basepath); // oh well, try cwd. basepath = MojoPlatform_currentWorkingDir(); } // else GBaseArchive = MojoArchive_newFromDirectory(basepath); // !!! FIXME: failing this, maybe default.mojosetup? } // if } // if } // else if (GBaseArchive == NULL) { free(basepath); basepath = NULL; } // if GBaseArchivePath = basepath; return GBaseArchive; } // MojoArchive_initBaseArchive void MojoArchive_deinitBaseArchive(void) { if (GBaseArchive != NULL) { GBaseArchive->close(GBaseArchive); GBaseArchive = NULL; } // if free((void *) GBaseArchivePath); GBaseArchivePath = NULL; } // MojoArchive_deinitBaseArchive // This stub is here if we didn't compile in libfetch... #if !SUPPORT_URL_HTTP && !SUPPORT_URL_FTP MojoInput *MojoInput_newFromURL(const char *url) { logError("No networking support in this build."); return NULL; } // MojoInput_newFromURL #endif // end of fileio.c ...