From ffa1836335b66a5024da18f840514b42cabacb9b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 15 Jul 2017 15:59:55 -0400 Subject: [PATCH] Moved ZIP tree management up to a higher level, to be reused elsewhere. --- src/archiver_zip.c | 396 ++++++++++++------------------------------ src/physfs.c | 170 ++++++++++++++++++ src/physfs_internal.h | 34 ++++ 3 files changed, 313 insertions(+), 287 deletions(-) diff --git a/src/archiver_zip.c b/src/archiver_zip.c index b27b795..0ed542d 100644 --- a/src/archiver_zip.c +++ b/src/archiver_zip.c @@ -57,7 +57,7 @@ typedef enum */ typedef struct _ZIPentry { - char *name; /* Name of file in archive */ + __PHYSFS_DirTreeEntry tree; /* manages directory tree */ struct _ZIPentry *symlink; /* NULL or file we symlink to */ ZipResolveType resolved; /* Have we resolved file/symlink? */ PHYSFS_uint64 offset; /* offset of data in archive */ @@ -70,9 +70,6 @@ typedef struct _ZIPentry PHYSFS_uint64 uncompressed_size; /* uncompressed size */ PHYSFS_sint64 last_mod_time; /* last file mod time */ PHYSFS_uint32 dos_mod_time; /* original MS-DOS style mod time */ - struct _ZIPentry *hashnext; /* next item in this hash bucket */ - struct _ZIPentry *children; /* linked list of kids, if dir */ - struct _ZIPentry *sibling; /* next item in same dir */ } ZIPentry; /* @@ -80,10 +77,8 @@ typedef struct _ZIPentry */ typedef struct { + __PHYSFS_DirTree tree; /* manages directory tree. */ PHYSFS_Io *io; /* the i/o interface for this archive. */ - ZIPentry root; /* root of directory tree. */ - ZIPentry **hash; /* all entries hashed for fast lookup. */ - size_t hashBuckets; /* number of buckets in hash. */ int zip64; /* non-zero if this is a Zip64 archive. */ int has_crypto; /* non-zero if any entry uses encryption. */ } ZIPinfo; @@ -269,14 +264,6 @@ static int zlib_err(const int rc) return rc; } /* zlib_err */ -/* - * Hash a string for lookup an a ZIPinfo hashtable. - */ -static inline PHYSFS_uint32 zip_hash_string(const ZIPinfo *info, const char *s) -{ - return __PHYSFS_hashString(s, strlen(s)) % info->hashBuckets; -} /* zip_hash_string */ - /* * Read an unsigned 64-bit int and swap to native byte order. */ @@ -648,42 +635,10 @@ static int isZip(PHYSFS_Io *io) } /* isZip */ -/* Find the ZIPentry for a path in platform-independent notation. */ -static ZIPentry *zip_find_entry(ZIPinfo *info, const char *path) -{ - PHYSFS_uint32 hashval; - ZIPentry *prev = NULL; - ZIPentry *retval; - - if (*path == '\0') - return &info->root; - - hashval = zip_hash_string(info, path); - for (retval = info->hash[hashval]; retval; retval = retval->hashnext) - { - if (strcmp(retval->name, path) == 0) - { - if (prev != NULL) /* move this to the front of the list */ - { - prev->hashnext = retval->hashnext; - retval->hashnext = info->hash[hashval]; - info->hash[hashval] = retval; - } /* if */ - - return retval; - } /* if */ - - prev = retval; - } /* for */ - - BAIL(PHYSFS_ERR_NOT_FOUND, NULL); -} /* zip_find_entry */ - - /* Convert paths from old, buggy DOS zippers... */ -static void zip_convert_dos_path(ZIPentry *entry, char *path) +static void zip_convert_dos_path(const PHYSFS_uint16 entryversion, char *path) { - PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((entry->version >> 8) & 0xFF); + const PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((entryversion >> 8) & 0xFF); if (hosttype == 0) /* FS_FAT_ */ { while (*path) @@ -754,6 +709,11 @@ static void zip_expand_symlink_path(char *path) } /* while */ } /* zip_expand_symlink_path */ +static inline ZIPentry *zip_find_entry(ZIPinfo *info, const char *path) +{ + return (ZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path); +} /* zip_find_entry */ + /* (forward reference: zip_follow_symlink and zip_resolve call each other.) */ static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry); @@ -834,7 +794,7 @@ static int zip_resolve_symlink(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry) if (rc) { path[entry->uncompressed_size] = '\0'; /* null-terminate it. */ - zip_convert_dos_path(entry, path); + zip_convert_dos_path(entry->version, path); entry->symlink = zip_follow_symlink(io, info, path); } /* else */ @@ -943,69 +903,6 @@ static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry) } /* zip_resolve */ -static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry); - -/* Fill in missing parent directories. */ -static ZIPentry *zip_hash_ancestors(ZIPinfo *info, char *name) -{ - ZIPentry *retval = &info->root; - char *sep = strrchr(name, '/'); - - if (sep) - { - const size_t namelen = (sep - name) + 1; - - *sep = '\0'; /* chop off last piece. */ - retval = zip_find_entry(info, name); - *sep = '/'; - - if (retval != NULL) - { - if (retval->resolved != ZIP_DIRECTORY) - BAIL(PHYSFS_ERR_CORRUPT, NULL); - return retval; /* already hashed. */ - } /* if */ - - /* okay, this is a new dir. Build and hash us. */ - retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + namelen); - BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); - memset(retval, '\0', sizeof (*retval)); - retval->name = ((char *) retval) + sizeof (ZIPentry); - memcpy(retval->name, name, namelen); - retval->name[namelen] = '\0'; - retval->resolved = ZIP_DIRECTORY; - if (!zip_hash_entry(info, retval)) - { - allocator.Free(retval); - return NULL; - } /* if */ - } /* else */ - - return retval; -} /* zip_hash_ancestors */ - - -static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry) -{ - PHYSFS_uint32 hashval; - ZIPentry *parent; - - assert(!zip_find_entry(info, entry->name)); /* checked elsewhere */ - - parent = zip_hash_ancestors(info, entry->name); - if (!parent) - return 0; - - hashval = zip_hash_string(info, entry->name); - entry->hashnext = info->hash[hashval]; - info->hash[hashval] = entry; - - entry->sibling = parent->children; - parent->children = entry; - return 1; -} /* zip_hash_entry */ - - static int zip_entry_is_symlink(const ZIPentry *entry) { return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) || @@ -1046,7 +943,8 @@ static int zip_version_does_symlinks(PHYSFS_uint32 version) } /* zip_version_does_symlinks */ -static int zip_has_symlink_attr(ZIPentry *entry, PHYSFS_uint32 extern_attr) +static inline int zip_has_symlink_attr(const ZIPentry *entry, + const PHYSFS_uint32 extern_attr) { PHYSFS_uint16 xattr = ((extern_attr >> 16) & 0xFFFF); return ( (zip_version_does_symlinks(entry->version)) && @@ -1081,9 +979,10 @@ static PHYSFS_sint64 zip_dos_time_to_physfs_time(PHYSFS_uint32 dostime) } /* zip_dos_time_to_physfs_time */ -static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64, +static ZIPentry *zip_load_entry(ZIPinfo *info, const int zip64, const PHYSFS_uint64 ofs_fixup) { + PHYSFS_Io *io = info->io; ZIPentry entry; ZIPentry *retval = NULL; PHYSFS_uint16 fnamelen, extralen, commentlen; @@ -1093,67 +992,83 @@ static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64, PHYSFS_uint16 ui16; PHYSFS_uint32 ui32; PHYSFS_sint64 si64; + char *name = NULL; + int isdir = 0; + + /* sanity check with central directory signature... */ + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); + BAIL_IF(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL); memset(&entry, '\0', sizeof (entry)); - /* sanity check with central directory signature... */ - if (!readui32(io, &ui32)) return NULL; - BAIL_IF(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL); - /* Get the pertinent parts of the record... */ - if (!readui16(io, &entry.version)) return NULL; - if (!readui16(io, &entry.version_needed)) return NULL; - if (!readui16(io, &entry.general_bits)) return NULL; /* general bits */ - if (!readui16(io, &entry.compression_method)) return NULL; - if (!readui32(io, &entry.dos_mod_time)) return NULL; + BAIL_IF_ERRPASS(!readui16(io, &entry.version), NULL); + BAIL_IF_ERRPASS(!readui16(io, &entry.version_needed), NULL); + BAIL_IF_ERRPASS(!readui16(io, &entry.general_bits), NULL); /* general bits */ + BAIL_IF_ERRPASS(!readui16(io, &entry.compression_method), NULL); + BAIL_IF_ERRPASS(!readui32(io, &entry.dos_mod_time), NULL); entry.last_mod_time = zip_dos_time_to_physfs_time(entry.dos_mod_time); - if (!readui32(io, &entry.crc)) return NULL; - if (!readui32(io, &ui32)) return NULL; + BAIL_IF_ERRPASS(!readui32(io, &entry.crc), NULL); + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); entry.compressed_size = (PHYSFS_uint64) ui32; - if (!readui32(io, &ui32)) return NULL; + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); entry.uncompressed_size = (PHYSFS_uint64) ui32; - if (!readui16(io, &fnamelen)) return NULL; - if (!readui16(io, &extralen)) return NULL; - if (!readui16(io, &commentlen)) return NULL; - if (!readui16(io, &ui16)) return NULL; + BAIL_IF_ERRPASS(!readui16(io, &fnamelen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &extralen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &commentlen), NULL); + BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL); starting_disk = (PHYSFS_uint32) ui16; - if (!readui16(io, &ui16)) return NULL; /* internal file attribs */ - if (!readui32(io, &external_attr)) return NULL; - if (!readui32(io, &ui32)) return NULL; + BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL); /* internal file attribs */ + BAIL_IF_ERRPASS(!readui32(io, &external_attr), NULL); + BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL); offset = (PHYSFS_uint64) ui32; - retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + fnamelen + 1); - BAIL_IF(retval == NULL, PHYSFS_ERR_OUT_OF_MEMORY, 0); - memcpy(retval, &entry, sizeof (*retval)); - retval->name = ((char *) retval) + sizeof (ZIPentry); + name = (char *) __PHYSFS_smallAlloc(fnamelen + 1); + BAIL_IF(!name, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + if (!__PHYSFS_readAll(io, name, fnamelen)) + { + __PHYSFS_smallFree(name); + return NULL; + } /* if */ - if (!__PHYSFS_readAll(io, retval->name, fnamelen)) - goto zip_load_entry_puked; + if (name[fnamelen - 1] == '/') + { + name[fnamelen - 1] = '\0'; + isdir = 1; + } /* if */ + name[fnamelen] = '\0'; /* null-terminate the filename. */ - retval->name[fnamelen] = '\0'; /* null-terminate the filename. */ - zip_convert_dos_path(retval, retval->name); + zip_convert_dos_path(entry.version, name); + + retval = (ZIPentry *) __PHYSFS_DirTreeAdd(&info->tree, name, isdir); + __PHYSFS_smallFree(name); + + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + + /* It's okay to BAIL without freeing retval, because it's stored in the + __PHYSFS_DirTree and will be freed later anyhow. */ + BAIL_IF(retval->last_mod_time != 0, PHYSFS_ERR_CORRUPT, NULL); /* dupe? */ + + /* Move the data we already read into place in the official object. */ + memcpy(((PHYSFS_uint8 *) retval) + sizeof (__PHYSFS_DirTreeEntry), + ((PHYSFS_uint8 *) &entry) + sizeof (__PHYSFS_DirTreeEntry), + sizeof (*retval) - sizeof (__PHYSFS_DirTreeEntry)); retval->symlink = NULL; /* will be resolved later, if necessary. */ - if (retval->name[fnamelen - 1] == '/') - { - retval->name[fnamelen - 1] = '\0'; + if (isdir) retval->resolved = ZIP_DIRECTORY; - } /* if */ else { - retval->resolved = (zip_has_symlink_attr(&entry, external_attr)) ? + retval->resolved = (zip_has_symlink_attr(retval, external_attr)) ? ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE; } /* else */ si64 = io->tell(io); - if (si64 == -1) - goto zip_load_entry_puked; + BAIL_IF_ERRPASS(si64 == -1, NULL); - /* - * The actual sizes didn't fit in 32-bits; look for the Zip64 - * extended information extra field... - */ + /* If the actual sizes didn't fit in 32-bits, look for the Zip64 + extended information extra field... */ if ( (zip64) && ((offset == 0xFFFFFFFF) || (starting_disk == 0xFFFFFFFF) || @@ -1164,17 +1079,14 @@ static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64, PHYSFS_uint16 sig, len; while (extralen > 4) { - if (!readui16(io, &sig)) - goto zip_load_entry_puked; - else if (!readui16(io, &len)) - goto zip_load_entry_puked; + BAIL_IF_ERRPASS(!readui16(io, &sig), NULL); + BAIL_IF_ERRPASS(!readui16(io, &len), NULL); si64 += 4 + len; extralen -= 4 + len; if (sig != ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG) { - if (!io->seek(io, si64)) - goto zip_load_entry_puked; + BAIL_IF_ERRPASS(!io->seek(io, si64), NULL); continue; } /* if */ @@ -1182,56 +1094,47 @@ static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64, break; } /* while */ - GOTO_IF(!found, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); + BAIL_IF(!found, PHYSFS_ERR_CORRUPT, NULL); if (retval->uncompressed_size == 0xFFFFFFFF) { - GOTO_IF(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui64(io, &retval->uncompressed_size)) - goto zip_load_entry_puked; + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &retval->uncompressed_size), NULL); len -= 8; } /* if */ if (retval->compressed_size == 0xFFFFFFFF) { - GOTO_IF(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui64(io, &retval->compressed_size)) - goto zip_load_entry_puked; + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &retval->compressed_size), NULL); len -= 8; } /* if */ if (offset == 0xFFFFFFFF) { - GOTO_IF(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui64(io, &offset)) - goto zip_load_entry_puked; + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui64(io, &offset), NULL); len -= 8; } /* if */ if (starting_disk == 0xFFFFFFFF) { - GOTO_IF(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui32(io, &starting_disk)) - goto zip_load_entry_puked; + BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL); + BAIL_IF_ERRPASS(!readui32(io, &starting_disk), NULL); len -= 4; } /* if */ - GOTO_IF(len != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); + BAIL_IF(len != 0, PHYSFS_ERR_CORRUPT, NULL); } /* if */ - GOTO_IF(starting_disk != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); + BAIL_IF(starting_disk != 0, PHYSFS_ERR_CORRUPT, NULL); retval->offset = offset + ofs_fixup; /* seek to the start of the next entry in the central directory... */ - if (!io->seek(io, si64 + extralen + commentlen)) - goto zip_load_entry_puked; + BAIL_IF_ERRPASS(!io->seek(io, si64 + extralen + commentlen), NULL); return retval; /* success. */ - -zip_load_entry_puked: - allocator.Free(retval); - return NULL; /* failure. */ } /* zip_load_entry */ @@ -1245,46 +1148,12 @@ static int zip_load_entries(ZIPinfo *info, const int zip64 = info->zip64; PHYSFS_uint64 i; - if (!io->seek(io, central_ofs)) - return 0; + BAIL_IF_ERRPASS(!io->seek(io, central_ofs), 0); for (i = 0; i < entry_count; i++) { - ZIPentry *entry = zip_load_entry(io, zip64, data_ofs); - ZIPentry *find; - - if (!entry) - return 0; - - find = zip_find_entry(info, entry->name); - if (find != NULL) /* duplicate? */ - { - if (find->last_mod_time != 0) /* duplicate? */ - { - allocator.Free(entry); - BAIL(PHYSFS_ERR_CORRUPT, 0); - } /* if */ - else /* we filled this in as a placeholder. Update it. */ - { - find->offset = entry->offset; - find->version = entry->version; - find->version_needed = entry->version_needed; - find->compression_method = entry->compression_method; - find->crc = entry->crc; - find->compressed_size = entry->compressed_size; - find->uncompressed_size = entry->uncompressed_size; - find->last_mod_time = entry->last_mod_time; - allocator.Free(entry); - continue; - } /* else */ - } /* if */ - - if (!zip_hash_entry(info, entry)) - { - allocator.Free(entry); - return 0; - } /* if */ - + ZIPentry *entry = zip_load_entry(info, zip64, data_ofs); + BAIL_IF_ERRPASS(!entry, 0); if (zip_entry_is_tradional_crypto(entry)) info->has_crypto = 1; } /* for */ @@ -1566,30 +1435,29 @@ static int zip_parse_end_of_central_dir(ZIPinfo *info, } /* zip_parse_end_of_central_dir */ -static int zip_alloc_hashtable(ZIPinfo *info, const PHYSFS_uint64 entry_count) +static void ZIP_closeArchive(void *opaque) { - size_t alloclen; + ZIPinfo *info = (ZIPinfo *) (opaque); - info->hashBuckets = (size_t) (entry_count / 5); - if (!info->hashBuckets) - info->hashBuckets = 1; + if (!info) + return; - alloclen = info->hashBuckets * sizeof (ZIPentry *); - info->hash = (ZIPentry **) allocator.Malloc(alloclen); - BAIL_IF(!info->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0); - memset(info->hash, '\0', alloclen); + if (info->io) + info->io->destroy(info->io); - return 1; -} /* zip_alloc_hashtable */ + __PHYSFS_DirTreeDeinit(&info->tree); + + allocator.Free(info); +} /* ZIP_closeArchive */ -static void ZIP_closeArchive(void *opaque); static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, int forWriting) { ZIPinfo *info = NULL; + ZIPentry *root = NULL; PHYSFS_uint64 dstart = 0; /* data start */ PHYSFS_uint64 cdir_ofs; /* central dir offset */ - PHYSFS_uint64 entry_count; + PHYSFS_uint64 count; assert(io != NULL); /* shouldn't ever happen. */ @@ -1599,17 +1467,21 @@ static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, int forWriting) info = (ZIPinfo *) allocator.Malloc(sizeof (ZIPinfo)); BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL); memset(info, '\0', sizeof (ZIPinfo)); - info->root.resolved = ZIP_DIRECTORY; + info->io = io; - if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &entry_count)) + if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &count)) goto ZIP_openarchive_failed; - else if (!zip_alloc_hashtable(info, entry_count)) - goto ZIP_openarchive_failed; - else if (!zip_load_entries(info, dstart, cdir_ofs, entry_count)) + else if (!__PHYSFS_DirTreeInit(&info->tree, count, sizeof (ZIPentry))) goto ZIP_openarchive_failed; - assert(info->root.sibling == NULL); + root = (ZIPentry *) info->tree.root; + root->resolved = ZIP_DIRECTORY; + + if (!zip_load_entries(info, dstart, cdir_ofs, count)) + goto ZIP_openarchive_failed; + + assert(info->tree.root->sibling == NULL); return info; ZIP_openarchive_failed: @@ -1619,23 +1491,6 @@ ZIP_openarchive_failed: } /* ZIP_openArchive */ -static void ZIP_enumerateFiles(void *opaque, const char *dname, - PHYSFS_EnumFilesCallback cb, - const char *origdir, void *callbackdata) -{ - ZIPinfo *info = ((ZIPinfo *) opaque); - const ZIPentry *entry = zip_find_entry(info, dname); - if (entry && (entry->resolved == ZIP_DIRECTORY)) - { - for (entry = entry->children; entry; entry = entry->sibling) - { - const char *ptr = strrchr(entry->name, '/'); - cb(callbackdata, origdir, ptr ? ptr + 1 : entry->name); - } /* for */ - } /* if */ -} /* ZIP_enumerateFiles */ - - static PHYSFS_Io *zip_get_io(PHYSFS_Io *io, ZIPinfo *inf, ZIPentry *entry) { int success; @@ -1764,39 +1619,6 @@ static PHYSFS_Io *ZIP_openAppend(void *opaque, const char *filename) } /* ZIP_openAppend */ -static void ZIP_closeArchive(void *opaque) -{ - ZIPinfo *info = (ZIPinfo *) (opaque); - - if (!info) - return; - - if (info->io) - info->io->destroy(info->io); - - assert(info->root.sibling == NULL); - assert(info->hash || (info->root.children == NULL)); - - if (info->hash) - { - size_t i; - for (i = 0; i < info->hashBuckets; i++) - { - ZIPentry *entry; - ZIPentry *next; - for (entry = info->hash[i]; entry; entry = next) - { - next = entry->hashnext; - allocator.Free(entry); - } /* for */ - } /* for */ - allocator.Free(info->hash); - } /* if */ - - allocator.Free(info); -} /* ZIP_closeArchive */ - - static int ZIP_remove(void *opaque, const char *name) { BAIL(PHYSFS_ERR_READ_ONLY, 0); @@ -1857,7 +1679,7 @@ const PHYSFS_Archiver __PHYSFS_Archiver_ZIP = 1, /* supportsSymlinks */ }, ZIP_openArchive, - ZIP_enumerateFiles, + __PHYSFS_DirTreeEnumerateFiles, ZIP_openRead, ZIP_openWrite, ZIP_openAppend, diff --git a/src/physfs.c b/src/physfs.c index cfd08a6..b228256 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -3007,5 +3007,175 @@ static void setDefaultAllocator(void) allocator.Free = mallocAllocatorFree; } /* setDefaultAllocator */ + +int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, + const PHYSFS_uint64 entry_count, + const size_t entrylen) +{ + static char rootpath[2] = { '/', '\0' }; + size_t alloclen; + + assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry)); + + memset(dt, '\0', sizeof (*dt)); + + dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen); + BAIL_IF(!dt->root, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memset(dt->root, '\0', entrylen); + dt->root->name = rootpath; + dt->root->isdir = 1; + dt->hashBuckets = (size_t) (entry_count / 5); + if (!dt->hashBuckets) + dt->hashBuckets = 1; + dt->entrylen = entrylen; + + alloclen = dt->hashBuckets * sizeof (__PHYSFS_DirTreeEntry *); + dt->hash = (__PHYSFS_DirTreeEntry **) allocator.Malloc(alloclen); + BAIL_IF(!dt->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memset(dt->hash, '\0', alloclen); + + return 1; +} /* __PHYSFS_DirTreeInit */ + + +static inline PHYSFS_uint32 hashPathName(__PHYSFS_DirTree *dt, const char *name) +{ + return __PHYSFS_hashString(name, strlen(name)) % dt->hashBuckets; +} /* hashPathName */ + + +/* Fill in missing parent directories. */ +static __PHYSFS_DirTreeEntry *addAncestors(__PHYSFS_DirTree *dt, char *name) +{ + __PHYSFS_DirTreeEntry *retval = dt->root; + char *sep = strrchr(name, '/'); + + if (sep) + { + const size_t namelen = (sep - name) + 1; + + *sep = '\0'; /* chop off last piece. */ + retval = (__PHYSFS_DirTreeEntry *) __PHYSFS_DirTreeFind(dt, name); + + if (retval != NULL) + { + *sep = '/'; + BAIL_IF(!retval->isdir, PHYSFS_ERR_CORRUPT, NULL); + return retval; /* already hashed. */ + } /* if */ + + /* okay, this is a new dir. Build and hash us. */ + retval = (__PHYSFS_DirTreeEntry*)__PHYSFS_DirTreeAdd(dt, name, 1); + *sep = '/'; + } /* if */ + + return retval; +} /* addAncestors */ + + +void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir) +{ + __PHYSFS_DirTreeEntry *retval = __PHYSFS_DirTreeFind(dt, name); + if (!retval) + { + const size_t alloclen = strlen(name) + 1 + dt->entrylen; + PHYSFS_uint32 hashval; + __PHYSFS_DirTreeEntry *parent = addAncestors(dt, name); + BAIL_IF_ERRPASS(!parent, NULL); + assert(dt->entrylen >= sizeof (__PHYSFS_DirTreeEntry)); + retval = (__PHYSFS_DirTreeEntry *) allocator.Malloc(alloclen); + BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memset(retval, '\0', dt->entrylen); + retval->name = ((char *) retval) + dt->entrylen; + strcpy(retval->name, name); + hashval = hashPathName(dt, name); + retval->hashnext = dt->hash[hashval]; + dt->hash[hashval] = retval; + retval->sibling = parent->children; + retval->isdir = isdir; + parent->children = retval; + } /* if */ + + return retval; +} /* __PHYSFS_DirTreeAdd */ + + +/* Find the __PHYSFS_DirTreeEntry for a path in platform-independent notation. */ +void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path) +{ + PHYSFS_uint32 hashval; + __PHYSFS_DirTreeEntry *prev = NULL; + __PHYSFS_DirTreeEntry *retval; + + if (*path == '\0') + return dt->root; + + hashval = hashPathName(dt, path); + for (retval = dt->hash[hashval]; retval; retval = retval->hashnext) + { + if (strcmp(retval->name, path) == 0) + { + if (prev != NULL) /* move this to the front of the list */ + { + prev->hashnext = retval->hashnext; + retval->hashnext = dt->hash[hashval]; + dt->hash[hashval] = retval; + } /* if */ + + return retval; + } /* if */ + + prev = retval; + } /* for */ + + BAIL(PHYSFS_ERR_NOT_FOUND, NULL); +} /* __PHYSFS_DirTreeFind */ + +void __PHYSFS_DirTreeEnumerateFiles(void *opaque, const char *dname, + PHYSFS_EnumFilesCallback cb, + const char *origdir, void *callbackdata) +{ + __PHYSFS_DirTree *tree = ((__PHYSFS_DirTree *) opaque); + const __PHYSFS_DirTreeEntry *entry = __PHYSFS_DirTreeFind(tree, dname); + if (entry && entry->isdir) + { + for (entry = entry->children; entry; entry = entry->sibling) + { + const char *ptr = strrchr(entry->name, '/'); + cb(callbackdata, origdir, ptr ? ptr + 1 : entry->name); + } /* for */ + } /* if */ +} /* __PHYSFS_DirTreeEnumerateFiles */ + + +void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt) +{ + if (!dt) + return; + + if (dt->root) + { + assert(dt->root->sibling == NULL); + assert(dt->hash || (dt->root->children == NULL)); + allocator.Free(dt->root); + } /* if */ + + if (dt->hash) + { + size_t i; + for (i = 0; i < dt->hashBuckets; i++) + { + __PHYSFS_DirTreeEntry *entry; + __PHYSFS_DirTreeEntry *next; + for (entry = dt->hash[i]; entry; entry = next) + { + next = entry->hashnext; + allocator.Free(entry); + } /* for */ + } /* for */ + allocator.Free(dt->hash); + } /* if */ +} /* __PHYSFS_DirTreeDeinit */ + /* end of physfs.c ... */ diff --git a/src/physfs_internal.h b/src/physfs_internal.h index 8fc72e8..75d2a8c 100644 --- a/src/physfs_internal.h +++ b/src/physfs_internal.h @@ -362,6 +362,40 @@ int UNPK_mkdir(void *opaque, const char *name); int UNPK_stat(void *opaque, const char *fn, PHYSFS_Stat *st); + +/* Optional API many archivers use this to manage their directory tree. */ +/* !!! FIXME: document this better. */ + +typedef struct __PHYSFS_DirTreeEntry +{ + char *name; /* Full path in archive. */ + struct __PHYSFS_DirTreeEntry *hashnext; /* next item in hash bucket. */ + struct __PHYSFS_DirTreeEntry *children; /* linked list of kids, if dir. */ + struct __PHYSFS_DirTreeEntry *sibling; /* next item in same dir. */ + int isdir; +} __PHYSFS_DirTreeEntry; + +typedef struct __PHYSFS_DirTree +{ + __PHYSFS_DirTreeEntry *root; /* root of directory tree. */ + __PHYSFS_DirTreeEntry **hash; /* all entries hashed for fast lookup. */ + size_t hashBuckets; /* number of buckets in hash. */ + size_t entrylen; /* size in bytes of entries (including subclass). */ +} __PHYSFS_DirTree; + + +int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, + const PHYSFS_uint64 entry_count, + const size_t entrylen); +void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir); +void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path); +void __PHYSFS_DirTreeEnumerateFiles(void *opaque, const char *dname, + PHYSFS_EnumFilesCallback cb, + const char *origdir, void *callbackdata); +void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt); + + + /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ /*------------ ----------------*/