/** * 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. */ #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 ...