This commit is contained in:
tkchia 2023-09-11 12:35:57 -07:00 committed by GitHub
commit 069273f2f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 160 additions and 55 deletions

View file

@ -152,10 +152,13 @@ ape_mz:
.short 0x0800 // MZ: increases cs load lower bound .short 0x0800 // MZ: increases cs load lower bound
.short 0x0040 // MZ: reloc table offset .short 0x0040 // MZ: reloc table offset
.short 0 // MZ: overlay number .short 0 // MZ: overlay number
.ascii "'<<'@'\n"
.org 0x24 // MZ: bytes reserved for you .org 0x24 // MZ: bytes reserved for you
.ascii "JT" // MZ: OEM identifier .ascii "JT" // MZ: OEM identifier
.short 0 // MZ: OEM information .short 1 // MZ: OEM information
.ascii "' <<'@'\n" __ape_com_sectors:
.short v_ape_allsectors // total number of sectors to load;
.endobj __ape_com_sectors,globl // to be patched by Cosmo tools
.org 0x40-4 // MZ: bytes reserved for you .org 0x40-4 // MZ: bytes reserved for you
#if SupportsWindows() || SupportsMetal() #if SupportsWindows() || SupportsMetal()
.long RVA(ape_pe) // PE: the new technology .long RVA(ape_pe) // PE: the new technology
@ -220,7 +223,7 @@ stub: mov $0x40,%dl // *literally* dos
nop // system five bootpoint nop // system five bootpoint
.org 0x48,0x90 // note ELF means JG 47 .org 0x48,0x90 // note ELF means JG 47
jmp 3f // MZ also means pop r10 jmp 3f // MZ also means pop r10
2: sub $8,%rsp // a.k.a. dec %ax sub %sp 2: push %rax // a.k.a. push %ax
xor %edx,%edx // MZ ate BIOS drive code xor %edx,%edx // MZ ate BIOS drive code
3: .byte 0xbd,0,0 // a.k.a. mov imm,%bp 3: .byte 0xbd,0,0 // a.k.a. mov imm,%bp
jmp pc // real mode, is real jmp pc // real mode, is real
@ -291,7 +294,11 @@ pc: cld
mov $1,%al // current sector mov $1,%al // current sector
xor %cx,%cx // current cylinder xor %cx,%cx // current cylinder
xor %dh,%dh // current head xor %dh,%dh // current head
mov $v_ape_realsectors,%di // total sectors mov $v_ape_maxrealsectors,%di // no. sectors for base memory
mov REAL(__ape_com_sectors),%bp
cmp %bp,%di
jb 3f
mov %bp,%di
3: call pcread 3: call pcread
mov %es,%si // addr += 512 mov %es,%si // addr += 512
add $512>>4,%si add $512>>4,%si
@ -417,13 +424,12 @@ pcread: push %ax
ror %cl ror %cl
or %al,%cl or %al,%cl
xor %bx,%bx // es:bx is destination addr xor %bx,%bx // es:bx is destination addr
mov $1,%al // read only one disk sector mov $0x0201,%ax // read only one disk sector
mov $2,%ah // read disk sectors ordinal
int $0x13 int $0x13
pop %cx pop %cx
pop %ax pop %ax
jc 9f jc 9f
inc %al // ++sector inc %ax // ++sector
cmp mm+"struct mman::pc_drive_last_sector",%al cmp mm+"struct mman::pc_drive_last_sector",%al
jbe 2f jbe 2f
mov $1,%al mov $1,%al
@ -480,7 +486,7 @@ sinit4: mov $4,%cx
jz 1f jz 1f
push %cx push %cx
push %si push %si
xchg %ax,%di xchg %ax,%dx
mov $REAL(sconf),%si mov $REAL(sconf),%si
call sinit call sinit
pop %si pop %si
@ -491,18 +497,16 @@ sinit4: mov $4,%cx
// Initializes Serial Line Communications 8250 UART 16550A // Initializes Serial Line Communications 8250 UART 16550A
// //
// @param word di tty port // @param word dx tty port
// @param char (*{es:,e,r}si)[4] register initial values // @param char (*{es:,e,r}si)[4] register initial values
// @clob dx
// @mode long,legacy,real // @mode long,legacy,real
// @see www.lammertbies.nl/comm/info/serial-uart.html // @see www.lammertbies.nl/comm/info/serial-uart.html
sinit: mov %di,%dx sinit: test %dx,%dx
test %dx,%dx
jz 2f jz 2f
push %dx push %dx
push %si push %si
xor %cx,%cx add $UART_LCR,%dx
mov $UART_LCR,%cl
add %cx,%dx
lodsb %ds:(%si),%al lodsb %ds:(%si),%al
pop %si pop %si
or $UART_DLAB,%al or $UART_DLAB,%al
@ -1515,7 +1519,11 @@ cpyhi: push %es
call unreal call unreal
mov $IMAGE_BASE_REAL,%esi mov $IMAGE_BASE_REAL,%esi
mov $IMAGE_BASE_PHYSICAL,%edi mov $IMAGE_BASE_PHYSICAL,%edi
mov $v_ape_realdwords,%ecx mov $v_ape_maxrealsectors,%ecx
cmp %bp,%cx
jb 1f
mov %bp,%cx
1: shl $9-2,%ecx // convert sector count to dword count
cld cld
rep movsl %ds:(%esi),%es:(%edi) rep movsl %ds:(%esi),%es:(%edi)
sti sti
@ -1545,13 +1553,16 @@ unreal: push %ds
pop %ds pop %ds
ret ret
// Reads any remaining program pages into memory which have not yet // Reads any remaining program & data sectors into memory which have not
// been read by the boot sector. // yet been read by the boot sector.
// //
// @clob eax, ecx, dx, esi, edi, bp // @return ebp physical address after end of loaded sectors
loadhi: mov $v_ape_highsectors,%bp // @clob eax, ecx, dx, esi, edi
test %bp,%bp loadhi:
jz 9f #define SEG 0x79000
mov $IMAGE_BASE_PHYSICAL+v_ape_maxrealbytes-SEG,%edi
sub $v_ape_maxrealsectors,%bp
jbe 9f
mov $mm+"struct mman::pc_drive",%si mov $mm+"struct mman::pc_drive",%si
cld cld
lodsb // pc_drive lodsb // pc_drive
@ -1565,8 +1576,6 @@ loadhi: mov $v_ape_highsectors,%bp
xchg %ax,%cx xchg %ax,%cx
mov (%si),%dh # pc_drive_next_head mov (%si),%dh # pc_drive_next_head
push %es push %es
#define SEG 0x79000
mov $IMAGE_BASE_PHYSICAL+v_ape_realbytes-SEG,%edi
push $SEG>>4 push $SEG>>4
pop %es pop %es
0: call pcread 0: call pcread
@ -1584,7 +1593,8 @@ loadhi: mov $v_ape_highsectors,%bp
dec %bp dec %bp
jnz 0b jnz 0b
pop %es pop %es
9: ret 9: lea SEG(%edi),%ebp
ret
// Initializes long mode paging. // Initializes long mode paging.
pinit: push %ds pinit: push %ds
@ -1638,6 +1648,8 @@ golong: cli
.endfn golong .endfn golong
// Long mode is long. // Long mode is long.
//
// @param ebp physical address after end of loaded sectors
.code64 .code64
long: movabs $BANE+PHYSICAL(0f),%rax long: movabs $BANE+PHYSICAL(0f),%rax
jmp *%rax jmp *%rax
@ -1653,12 +1665,12 @@ long: movabs $BANE+PHYSICAL(0f),%rax
xor %r13d,%r13d xor %r13d,%r13d
xor %r14d,%r14d xor %r14d,%r14d
xor %r15d,%r15d xor %r15d,%r15d
xor %ebx,%ebx
xor %ebp,%ebp
mov $mm,%rdi mov $mm,%rdi
mov %cr3,%rsi mov %cr3,%rsi
mov $IMAGE_BASE_PHYSICAL,%edx mov $IMAGE_BASE_PHYSICAL,%edx
lea v_ape_allbytes(%rdx),%ecx mov %ebp,%ecx
xor %ebx,%ebx
xor %ebp,%ebp
call __map_phdrs call __map_phdrs
push $0x037f push $0x037f
fldcw (%rsp) fldcw (%rsp)
@ -1792,9 +1804,9 @@ kernel: movabs $ape_stack_vaddr,%rsp
.endm .endm
.ldsvar _end .ldsvar _end
.ldsvar _etext .ldsvar _etext
.ldsvar v_ape_realsectors .ldsvar v_ape_maxrealsectors
.ldsvar v_ape_realbytes .ldsvar v_ape_maxrealbytes
.ldsvar v_ape_highsectors .ldsvar v_ape_allsectors
.ldsvar ape_idata_ro .ldsvar ape_idata_ro
.ldsvar ape_piro .ldsvar ape_piro
.ldsvar ape_piro_end .ldsvar ape_piro_end

View file

@ -436,7 +436,6 @@ SECTIONS {
/*END: NT FORK COPYING */ /*END: NT FORK COPYING */
_edata = .; _edata = .;
PROVIDE(edata = .); PROVIDE(edata = .);
_ezip = .; /* <-- very deprecated */
} :Ram } :Ram
. = ALIGN(CONSTANT(COMMONPAGESIZE)); . = ALIGN(CONSTANT(COMMONPAGESIZE));
@ -633,12 +632,10 @@ APE_DECLARE_FIXED_DECIMAL(IDENTITY, blink_xnu_aarch64_size);
#endif /* APE_IS_SHELL_SCRIPT */ #endif /* APE_IS_SHELL_SCRIPT */
#if SupportsMetal() #if SupportsMetal()
v_ape_realsectors = MIN(0x70000 - IMAGE_BASE_REAL, ROUNDUP(RVA(_ezip), 512)) / 512; v_ape_maxrealbytes = ROUNDUP(0x70000 - IMAGE_BASE_REAL, 512);
v_ape_realbytes = v_ape_realsectors * 512; v_ape_maxrealsectors = v_ape_maxrealbytes / 512;
v_ape_realdwords = v_ape_realsectors * (512 / 4); v_ape_allbytes = ROUNDUP(_edata - __executable_start, 512);
v_ape_allsectors = ROUNDUP(RVA(_ezip), 512) / 512; v_ape_allsectors = v_ape_allbytes / 512;
v_ape_allbytes = v_ape_allsectors * 512;
v_ape_highsectors = MIN(0xffff, v_ape_allsectors - v_ape_realsectors);
TSSDESCSTUB2(_tss, _tss, _tss_end ? _tss_end - _tss - 1 : 0); TSSDESCSTUB2(_tss, _tss, _tss_end ? _tss_end - _tss - 1 : 0);
#endif #endif

View file

@ -9,7 +9,6 @@ extern unsigned char __privileged_start[] __attribute__((__weak__));
extern unsigned char _ehead[] __attribute__((__weak__)); extern unsigned char _ehead[] __attribute__((__weak__));
extern unsigned char _etext[] __attribute__((__weak__)); extern unsigned char _etext[] __attribute__((__weak__));
extern unsigned char _edata[] __attribute__((__weak__)); extern unsigned char _edata[] __attribute__((__weak__));
extern unsigned char _ezip[] __attribute__((__weak__));
extern unsigned char _end[] __attribute__((__weak__)); extern unsigned char _end[] __attribute__((__weak__));
extern unsigned char _ereal[] __attribute__((__weak__)); extern unsigned char _ereal[] __attribute__((__weak__));
extern unsigned char _tdata_start[] __attribute__((__weak__)); extern unsigned char _tdata_start[] __attribute__((__weak__));

Binary file not shown.

View file

@ -17,22 +17,33 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/metalfile.internal.h"
#include "libc/calls/struct/stat.h" #include "libc/calls/struct/stat.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/s.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
int sys_fstat_metal(int fd, struct stat *st) { int sys_fstat_metal(int fd, struct stat *st) {
if (fd < 0) return einval(); struct MetalFile *file;
if (fd < g_fds.n && g_fds.p[fd].kind == kFdSerial) { if (fd < 0 || fd >= g_fds.n) return einval();
bzero(st, sizeof(*st)); switch (g_fds.p[fd].kind) {
st->st_dev = g_fds.p[fd].handle; case kFdSerial:
st->st_rdev = g_fds.p[fd].handle; bzero(st, sizeof(*st));
st->st_nlink = 1; st->st_dev = g_fds.p[fd].handle;
st->st_mode = S_IFCHR | 0600; st->st_rdev = g_fds.p[fd].handle;
st->st_blksize = 1; st->st_nlink = 1;
return 0; st->st_mode = S_IFCHR | 0600;
} else { st->st_blksize = 1;
return ebadf(); return 0;
case kFdFile:
file = (struct MetalFile *)g_fds.p[fd].handle;
bzero(st, sizeof(*st));
st->st_nlink = 1;
st->st_size = file->size;
st->st_mode = S_IFREG | 0600;
st->st_blksize = 1;
return 0;
default:
return ebadf();
} }
} }

View file

@ -44,6 +44,7 @@ forceinline bool __isfdkind(int fd, int kind) {
int _check_interrupts(int); int _check_interrupts(int);
int sys_close_nt(struct Fd *, int); int sys_close_nt(struct Fd *, int);
int sys_openat_metal(int, const char *, int, unsigned); int sys_openat_metal(int, const char *, int, unsigned);
int64_t sys_lseek_metal(struct Fd *, int64_t, int);
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

50
libc/calls/lseek-metal.c Normal file
View file

@ -0,0 +1,50 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/metalfile.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/sysv/errfuns.h"
int64_t sys_lseek_metal(struct Fd *fd, int64_t offset, int whence) {
size_t new_pos;
struct MetalFile *file;
switch (fd->kind) {
case kFdFile:
file = (struct MetalFile *)fd->handle;
switch (whence) {
case SEEK_SET:
new_pos = offset;
break;
case SEEK_CUR:
new_pos = file->pos + offset;
break;
case SEEK_END:
new_pos = file->size + offset;
break;
default:
return einval();
}
if (new_pos > file->size) return einval();
file->pos = new_pos;
return new_pos;
default:
return ebadf();
}
}

View file

@ -77,9 +77,11 @@
*/ */
int64_t lseek(int fd, int64_t offset, int whence) { int64_t lseek(int fd, int64_t offset, int whence) {
int64_t rc; int64_t rc;
if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) { if (fd >= 0 && fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
rc = _weaken(__zipos_seek)( rc = _weaken(__zipos_seek)(
(struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle, offset, whence); (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle, offset, whence);
} else if (fd >= 0 && fd < g_fds.n && IsMetal()) {
rc = sys_lseek_metal(g_fds.p + fd, offset, whence);
} else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd()) { } else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd()) {
rc = sys_lseek(fd, offset, whence, 0); rc = sys_lseek(fd, offset, whence, 0);
} else if (IsNetbsd()) { } else if (IsNetbsd()) {

View file

@ -53,7 +53,7 @@ void *__ape_com_base;
size_t __ape_com_size = 0; size_t __ape_com_size = 0;
textstartup dontasan void InitializeMetalFile(void) { textstartup dontasan void InitializeMetalFile(void) {
if (IsMetal()) { if (IsMetal() && _weaken(__ape_com_sectors)) {
/* /*
* Copy out a pristine image of the program before the program might * Copy out a pristine image of the program before the program might
* decide to modify its own .data section. * decide to modify its own .data section.
@ -63,8 +63,7 @@ textstartup dontasan void InitializeMetalFile(void) {
* The zipos code will automatically arrange to do this. Alternatively, * The zipos code will automatically arrange to do this. Alternatively,
* user code can __static_yoink this symbol. * user code can __static_yoink this symbol.
*/ */
size_t size = ROUNDUP(_ezip - __executable_start, 4096); size_t size = ROUNDUP((size_t)__ape_com_sectors * 512, 4096);
// TODO(jart): Restore support for ZIPOS on metal.
void *copied_base; void *copied_base;
struct DirectMap dm; struct DirectMap dm;
dm = sys_mmap_metal(NULL, size, PROT_READ | PROT_WRITE, dm = sys_mmap_metal(NULL, size, PROT_READ | PROT_WRITE,

View file

@ -11,6 +11,7 @@ struct MetalFile {
extern void *__ape_com_base; extern void *__ape_com_base;
extern size_t __ape_com_size; extern size_t __ape_com_size;
extern uint16_t __ape_com_sectors; // ape/ape.S
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/metalfile.internal.h" #include "libc/calls/metalfile.internal.h"
@ -46,6 +47,10 @@ dontasan struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot,
struct mman *mm; struct mman *mm;
struct DirectMap res; struct DirectMap res;
uint64_t addr, faddr = 0, page, e, *pte, *fdpte, *pml4t; uint64_t addr, faddr = 0, page, e, *pte, *fdpte, *pml4t;
if ((uintptr_t)BANE + size < (uintptr_t)BANE) {
assert(false);
return bad_mmap();
}
mm = __get_mm(); mm = __get_mm();
pml4t = __get_pml4t(); pml4t = __get_pml4t();
size = ROUNDUP(size, 4096); size = ROUNDUP(size, 4096);
@ -69,11 +74,14 @@ dontasan struct DirectMap sys_mmap_metal(void *vaddr, size_t size, int prot,
} else { } else {
addr = ROUNDUP(addr, 4096); addr = ROUNDUP(addr, 4096);
} }
for (i = 0; i < size; i += 4096) { i = 0;
while (i < size) {
pte = __get_virtual(mm, pml4t, addr + i, false); pte = __get_virtual(mm, pml4t, addr + i, false);
if (pte && (*pte & (PAGE_V | PAGE_RSRV))) { if (pte && (*pte & (PAGE_V | PAGE_RSRV))) {
addr = MAX(addr, sys_mmap_metal_break) + i + 4096; addr = MAX(addr, sys_mmap_metal_break) + i + 4096;
i = 0; i = 0;
} else {
i += 4096;
} }
} }
sys_mmap_metal_break = MAX(addr + size, sys_mmap_metal_break); sys_mmap_metal_break = MAX(addr + size, sys_mmap_metal_break);

View file

@ -25,6 +25,7 @@
OTHER DEALINGS IN THE SOFTWARE. OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/runtime/pc.internal.h" #include "libc/runtime/pc.internal.h"

View file

@ -23,11 +23,15 @@
#include "libc/elf/struct/shdr.h" #include "libc/elf/struct/shdr.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/magnumstrs.internal.h" #include "libc/fmt/magnumstrs.internal.h"
#include "libc/intrin/newbie.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/nt/struct/imagedosheader.internal.h"
#include "libc/runtime/pc.internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/prot.h"
#include "libc/zip.internal.h" #include "libc/zip.internal.h"
@ -109,6 +113,25 @@ static void GetOpts(int argc, char *argv[]) {
outpath = argv[optind + 1]; outpath = argv[optind + 1];
} }
static void BiosZipFix(unsigned long cdest) {
struct NtImageDosHeader head;
size_t hd_sz = sizeof(head);
size_t e_oemid = offsetof(struct NtImageDosHeader, e_oemid);
size_t e_oeminfo = offsetof(struct NtImageDosHeader, e_oeminfo);
if (outsize < hd_sz) return;
if (pread(outfd, &head, hd_sz, 0) != hd_sz) return;
if (READ64LE((uint8_t *)&head) != READ64LE("MZqFpD='") &&
READ64LE((uint8_t *)&head) != READ64LE("jartsr='")) return;
if (le16toh(head.e_oemid) != READ16LE("JT") ||
le16toh(head.e_oeminfo) != 1) return;
// patch executable head so that it will load the right count of sectors
// when run as a legacy BIOS disk image
head.e_res2[0] = htole16(ROUNDUP(cdest, 0x200) / 0x200);
if (pwrite(outfd, &head, hd_sz, 0) != hd_sz) {
SysDie(outpath, "head pwrite");
}
}
static void CopyZip(void) { static void CopyZip(void) {
char *secstrs; char *secstrs;
int rela, recs; int rela, recs;
@ -176,7 +199,7 @@ static void CopyZip(void) {
} }
// write output // write output
if ((outfd = open(outpath, O_WRONLY | O_CREAT, 0644)) == -1) { if ((outfd = open(outpath, O_RDWR | O_CREAT, 0644)) == -1) {
SysDie(outpath, "open"); SysDie(outpath, "open");
} }
if ((outsize = lseek(outfd, 0, SEEK_END)) == -1) { if ((outsize = lseek(outfd, 0, SEEK_END)) == -1) {
@ -205,6 +228,7 @@ static void CopyZip(void) {
if (pwrite(outfd, eocd, length, cdest) != length) { if (pwrite(outfd, eocd, length, cdest) != length) {
SysDie(outpath, "eocd pwrite"); SysDie(outpath, "eocd pwrite");
} }
BiosZipFix(cdest);
if (close(outfd)) { if (close(outfd)) {
SysDie(outpath, "close"); SysDie(outpath, "close");
} }