Fix js-dos ZIP extraction: write dirs shallowest-first, then files
Build & Deploy / build-and-deploy (push) Successful in 1m40s
Build & Deploy / build-and-deploy (push) Successful in 1m40s
js-dos v8's C extraction code (jsdos-libzip.c) iterates ZIP entries
linearly and calls mkdir() on each directory entry. If a child
directory appears before its parent, mkdir() fails with perror()
and exit(1) — fatal crash with 'No such file or directory'.
Previous approach wrote directory entries interleaved with files,
deepest-first (from the file path right-to-left). Now:
Phase 1: Walk entire file tree to discover ALL directories
Phase 2: Write ALL dir entries sorted (shallowest first via TreeSet
natural ordering — '/' sorts before any letter)
Phase 3: Write .jsdos/ config files
Phase 4: Write game files (parents already exist from Phase 2)
This guarantees that mkdir("NAS50TH/TRACKS/") runs before
mkdir("NAS50TH/TRACKS/WILKES/"), which runs before open().
This commit is contained in:
@@ -296,47 +296,46 @@ public class UploadResource {
|
||||
|
||||
// ZIP it up directly from extractDir (no temp copy)
|
||||
try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) {
|
||||
var addedDirs = new java.util.HashSet<String>();
|
||||
|
||||
// Helper: write a directory entry and all its ancestors if not yet added
|
||||
// (Emscripten FS requires every directory in the chain as explicit entries)
|
||||
java.util.function.Consumer<String> ensureDir = entryName -> {
|
||||
int idx = entryName.lastIndexOf('/');
|
||||
while (idx >= 0) {
|
||||
String parent = entryName.substring(0, idx + 1);
|
||||
if (addedDirs.add(parent)) {
|
||||
// ── Phase 1: Collect all directory paths from the file tree ──
|
||||
// js-dos C code (jsdos-libzip.c) iterates ZIP entries linearly and
|
||||
// calls mkdir() on each directory entry. All parents must come
|
||||
// before their children or mkdir() fails with exit(1).
|
||||
var allDirs = new java.util.TreeSet<String>();
|
||||
try (var walk = Files.walk(extractDir)) {
|
||||
walk.filter(Files::isRegularFile).forEach(f -> {
|
||||
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
|
||||
int idx = entryName.lastIndexOf('/');
|
||||
while (idx >= 0) {
|
||||
allDirs.add(entryName.substring(0, idx + 1));
|
||||
idx = entryName.lastIndexOf('/', idx - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Phase 2: Write ALL directory entries first (sorted → shallowest first) ──
|
||||
for (String dir : allDirs) {
|
||||
zos.putNextEntry(new java.util.zip.ZipEntry(dir));
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
// ── Phase 3: .jsdos config files (must come before game files) ──
|
||||
try (var walk = Files.walk(jsdos)) {
|
||||
walk.filter(Files::isRegularFile)
|
||||
.sorted()
|
||||
.forEach(f -> {
|
||||
try {
|
||||
zos.putNextEntry(new java.util.zip.ZipEntry(parent));
|
||||
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
|
||||
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
|
||||
Files.copy(f, zos);
|
||||
zos.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
idx = entryName.lastIndexOf('/', idx - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper: write a single file entry with its directory chain
|
||||
java.util.function.Consumer<Path> writeFile = f -> {
|
||||
try {
|
||||
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
|
||||
ensureDir.accept(entryName);
|
||||
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
|
||||
Files.copy(f, zos);
|
||||
zos.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
};
|
||||
|
||||
// ── First pass: .jsdos config files (must come first in ZIP) ──
|
||||
try (var walk = Files.walk(jsdos)) {
|
||||
walk.filter(Files::isRegularFile)
|
||||
.sorted() // deterministic order
|
||||
.forEach(writeFile);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Second pass: all other game files ──
|
||||
// ── Phase 4: All other game files ──
|
||||
try (var walk = Files.walk(extractDir)) {
|
||||
walk.filter(Files::isRegularFile)
|
||||
.filter(f -> !f.startsWith(jsdos))
|
||||
@@ -345,8 +344,17 @@ public class UploadResource {
|
||||
int dot = n.lastIndexOf('.');
|
||||
return dot < 0 || !SKIP_EXT.contains(n.substring(dot));
|
||||
})
|
||||
.sorted() // deterministic order
|
||||
.forEach(writeFile);
|
||||
.sorted()
|
||||
.forEach(f -> {
|
||||
try {
|
||||
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
|
||||
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
|
||||
Files.copy(f, zos);
|
||||
zos.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user