From 7be70022dd3ae138998fc7185351b9b49ad9a9b2 Mon Sep 17 00:00:00 2001 From: hpa Date: Wed, 15 Dec 2004 10:14:39 +0000 Subject: [PATCH] Prepping for new 2.20 version: rewrite main syslinux program to support FAT32 and EDD, and a new cleaner installer infrastructure. --- Makefile | 2 +- bootsect.inc | 2 +- comboot.inc | 2 +- syslinux.asm => dos/syslinux.asm | 6 +- findpatch.pl | 28 -- ldlinux.asm | 926 +++++++++++++++++++++------------------ libfat/cache.c | 70 +++ libfat/fat.h | 112 +++++ libfat/fatchain.c | 136 ++++++ libfat/libfat.h | 81 ++++ libfat/libfatint.h | 56 +++ libfat/open.c | 118 +++++ libfat/searchdir.c | 61 +++ libfat/ulint.h | 115 +++++ mtools/Makefile | 39 ++ syslinux.c => mtools/syslinux.c | 57 ++- syslinux.h | 5 +- syslxmod.c | 82 +++- unix/Makefile | 39 ++ unix/syslinux.c | 487 ++++++++++++++++++++ version | 2 +- 21 files changed, 1952 insertions(+), 474 deletions(-) rename syslinux.asm => dos/syslinux.asm (98%) delete mode 100755 findpatch.pl create mode 100644 libfat/cache.c create mode 100644 libfat/fat.h create mode 100644 libfat/fatchain.c create mode 100644 libfat/libfat.h create mode 100644 libfat/libfatint.h create mode 100644 libfat/open.c create mode 100644 libfat/searchdir.c create mode 100644 libfat/ulint.h create mode 100644 mtools/Makefile rename syslinux.c => mtools/syslinux.c (80%) create mode 100644 unix/Makefile create mode 100644 unix/syslinux.c diff --git a/Makefile b/Makefile index 6706c11..afc30a4 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ VERSION = $(shell cat version) $(CC) $(INCLUDE) $(CFLAGS) -c $< # libsyslinux.so -LIB_SONAME = libsyslinux.so.2 +LIB_SONAME = libsyslinux.so.2.2 LIB_SO = libsyslinux.so.$(VERSION) # diff --git a/bootsect.inc b/bootsect.inc index 654ab6b..f901cf5 100644 --- a/bootsect.inc +++ b/bootsect.inc @@ -65,7 +65,7 @@ load_bootsec: mov eax,[OrigFDCTabPtr] mov [fdctab],eax - mov dl,[bsDriveNumber] + mov dl,[DriveNumber] mov si,PartInfo ; Partition info buffer mov di,800h-18 ; Put partition info here push di diff --git a/comboot.inc b/comboot.inc index af2ea0c..f3aec3b 100644 --- a/comboot.inc +++ b/comboot.inc @@ -475,7 +475,7 @@ comapi_pxecall equ comapi_err ; Not available comapi_derinfo: mov P_AL,my_id %if IS_SYSLINUX || IS_MDSLINUX - mov al,[bsDriveNumber] + mov al,[DriveNumber] mov P_DL,al mov P_ES,cs mov P_BX,PartInfo diff --git a/syslinux.asm b/dos/syslinux.asm similarity index 98% rename from syslinux.asm rename to dos/syslinux.asm index 25d7339..9b61f24 100644 --- a/syslinux.asm +++ b/dos/syslinux.asm @@ -328,9 +328,9 @@ read_bootsect: pop ax ; Remove flags from stack jc disk_read_error - mov si,SectorBuffer+11 ; Offset of superblock - mov di,BootSector+11 - mov cx,51 ; Superblock = 51 bytes + mov si,SectorBuffer+3 ; Offset of superblock + mov di,BootSector+3 + mov cx,59 ; Superblock = 59 bytes rep movsb ; Copy the superblock jmp short write_bootsect disk_read_error: diff --git a/findpatch.pl b/findpatch.pl deleted file mode 100755 index 551d874..0000000 --- a/findpatch.pl +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/perl -# -# Script to find the "patch area" of ldlinux.sys -# - -eval { use bytes; }; - -open(SYS, "< ldlinux.sys") or die "$0: Cannot open ldlinux.sys\n"; -eval { binmode SYS; }; -if ( read(SYS,$sec1,512) != 512 ) { - die "$0: ldlinux.sys: short read\n"; -} -close(SYS); - -for ( $i = 0 ; $i < 512; $i++ ) { - $scan = substr($sec1,$i,12); - - if ( $scan eq "\032LDLINUX SYS" && - substr($sec1,$i+16,2) eq "\x55\xAA" ) { - last; - } -} - - -die "$0: Did not find patch area signature\n" unless ( $i < 512 ); - -# Past signature, plus align to the subsequent dword. -print ((($i+18)+3) & ~3); print "\n"; diff --git a/ldlinux.asm b/ldlinux.asm index 7dcfb57..2e6503e 100644 --- a/ldlinux.asm +++ b/ldlinux.asm @@ -44,6 +44,10 @@ FILENAME_MAX equ 11 ; Max mangled filename size NULLFILE equ ' ' ; First char space == null filename retry_count equ 6 ; How patient are we with the disk? %assign HIGHMEM_SLOP 0 ; Avoid this much memory near the top +LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with + +SECTOR_SHIFT equ 9 +SECTOR_SIZE equ (1 << SECTOR_SHIFT) ; ; This is what we need to do when idle @@ -91,8 +95,8 @@ vk_end: equ $ ; Should be <= vk_size ; ; 0000h - main code/data segment (and BIOS segment) ; -real_mode_seg equ 5000h -fat_seg equ 3000h ; 128K area for FAT (2x64K) +real_mode_seg equ 4000h +cache_seg equ 3000h ; 64K area for metadata cache vk_seg equ 2000h ; Virtual kernels xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem comboot_seg equ real_mode_seg ; COMBOOT image loading zone @@ -187,6 +191,8 @@ VGAPos resw 1 ; Pointer into VGA memory VGACluster resw 1 ; Cluster pointer for VGA image file VGAFilePtr resw 1 ; Pointer into VGAFileBuf Com32SysSP resw 1 ; SP saved during COM32 syscall +CachePtrs times (65536/SECTOR_SIZE) resw 1 +NextCacheSlot resw 1 CursorDX equ $ CursorCol resb 1 ; Cursor column for message file CursorRow resb 1 ; Cursor row for message file @@ -206,6 +212,7 @@ A20Tries resb 1 ; Times until giving up on A20 FuncFlag resb 1 ; Escape sequences received from keyboard DisplayMask resb 1 ; Display modes mask CopySuper resb 1 ; Distinguish .bs versus .bss +DriveNumber resb 1 ; BIOS drive number MNameBuf resb 11 ; Generic mangled file name buffer InitRD resb 11 ; initrd= mangled name KernelCName resb 13 ; Unmangled kernel name @@ -278,12 +285,11 @@ superblock equ $ superinfo_size equ ($-superblock)-1 ; How much to expand superd Hidden superd HugeSectors - superb DriveNumber - superb Reserved1 - superb BootSignature ; 29h if the following fields exist - superd VolumeID -bsVolumeLabel zb 11 -bsFileSysType zb 8 ; Must be FAT12 or FAT16 for this version + ; + ; This is as far as FAT12/16 and FAT32 are consistent + ; + zb 54 ; FAT12/16 need 26 more bytes, + ; FAT32 need 54 more bytes superblock_len equ $-superblock SecPerClust equ bxSecPerClust @@ -313,7 +319,6 @@ start: mov ds,ax ; Now we can initialize DS... - mov [di+bsDriveNumber-FloppyTable],dl ; ; Now sautee the BIOS floppy info block to that it will support decent- ; size transfers; the floppy block is 11 bytes and is stored in the @@ -329,6 +334,7 @@ start: ; Save the old fdctab even if hard disk so the stack layout ; is the same. The instructions above do not change the flags + mov [DriveNumber],dl ; Save drive number in DL and dl,dl ; If floppy disk (00-7F), assume no ; partition table js harddisk @@ -384,86 +390,45 @@ not_harddisk: ; Ready to enable interrupts, captain ; sti -; -; Insane hack to expand the superblock to dwords -; -expand_super: - xor eax,eax - mov es,ax ; INT 13:08 destroys ES - mov si,superblock - mov di,SuperInfo - mov cl,superinfo_size ; CH == 0 -.loop: - lodsw - dec si - stosd ; Store expanded word - xor ah,ah - stosd ; Store expanded byte - loop .loop + ; -; Now we have to do some arithmetric to figure out where things are located. -; If Micro$oft had had brains they would already have done this for us, -; and stored it in the superblock at format time, but here we go, -; wasting precious boot sector space again... +; Do we have EBIOS (EDD)? ; -%define Z di-superinfo_size*8-SuperInfo -debugentrypt: - mov ax,[bxFATs] ; Number of FATs (eax<31:16> == 0) - mov edx,[Z+bxFATsecs] ; Sectors/FAT - mul edx ; Get the size of the FAT area - ; edx <- 0 - add eax,[bxHidden] ; Add hidden sectors - add eax,[Z+bxResSectors] ; And reserved sectors - - mov [RootDir],eax ; Location of root directory - mov [DataArea],eax ; First data sector - push eax - - mov eax,[Z+bxRootDirEnts] - shl ax,5 ; Size of a directory entry - mov bx,[Z+bxBytesPerSec] - add ax,bx ; Round up, not down - dec ax - div bx ; Now we have the size of the root dir - mov [RootDirSize],ax - mov [DirScanCtr],ax - add bx,trackbuf-31 - mov [Z+EndofDirSec],bx ; End of a single directory sector - add [Z+DataArea],eax - pop eax ; Reload root directory starting point +eddcheck: + mov bx,55AAh + mov ah,41h ; EDD existence query + mov dl,[DriveNumber] + int 13h + jc .noedd + cmp bx,0AA55h + jne .noedd + test cl,1 ; Extended disk access functionality set + jz .noedd + ; + ; We have EDD support... + ; + mov byte [getlinsec+1],getlinsec_ebios-(getlinsec+2) +.noedd: ; -; Now the fun begins. We have to search the root directory for -; LDLINUX.SYS and load the first sector, so we have a little more -; space to have fun with. Then we can go chasing through the FAT. -; Joy!! +; Load the first sector of LDLINUX.SYS; this used to be all proper +; with parsing the superblock and root directory; it doesn't fit +; together with EBIOS support, unfortunately. ; -sd_nextsec: push eax - mov bx,trackbuf - push bx + mov eax,[FirstSector] ; Sector start + mov bx,ldlinux_sys ; Where to load it call getonesec - pop si -sd_nextentry: mov cx,11 - cmp [si],ch ; Directory high water mark - je kaboom -; This no longer fits... since we'd be dead anyway if there -; was a nonfile named LDLINUX.SYS on the disk, it shouldn't -; matter... -; test byte [si+11],18h ; Must be a file -; jnz sd_not_file - mov di,ldlinux_name - push si - repe cmpsb - pop si - je found_it -sd_not_file: add si,byte 32 ; Distance to next - cmp si,[EndofDirSec] - jb sd_nextentry - pop eax - inc eax - dec word [DirScanCtr] - jnz sd_nextsec + + ; Some modicum of integrity checking + cmp dword [ldlinux_magic],LDLINUX_MAGIC + jne kaboom + cmp dword [ldlinux_magic+4],HEXDATE + jne kaboom + + ; Go for it... + jmp ldlinux_ent + ; ; kaboom: write a message and bail out. ; @@ -481,31 +446,6 @@ kaboom: .norge: jmp short .norge ; If int 19h returned; this is the end ; -; found_it: now we compute the location of the first sector, then -; load it and JUMP (since we're almost out of space) -; -found_it: ; Note: we actually leave two words on the stack here - ; (who cares?) - mov eax,[bxSecPerClust] - mov bp,ax ; Load an entire cluster - movzx ebx,word [si+26] ; First cluster - mov [RunLinClust],bx ; Save for later use - dec bx ; First cluster is "cluster 2" - dec bx - mul ebx - add eax,[DataArea] - mov bx,ldlinux_sys - call getlinsec - mov si,bs_magic - mov di,ldlinux_magic - mov cx,magic_len - repe cmpsb ; Make sure that the bootsector - jne kaboom ; matches LDLINUX.SYS -; -; Done! Jump to the entry point! -; - jmp ldlinux_ent -; ; ; writestr: write a null-terminated string to the console ; This assumes we're on page 0. This is only used for early @@ -522,21 +462,28 @@ writestr: .return: ret ; -; disk_error: decrement the retry count and bail if zero. -; This gets patched once we have more space to try to -; optimize transfer sizes on broken machines. +; xint13: wrapper for int 13h which will retry 6 times and then die, +; AND save all registers except BP ; -disk_error: dec si ; SI holds the disk retry counter - jz kaboom - ; End of patched "call" instruction! - jmp short disk_try_again +xint13: +.again: + mov bp,retry_count +.loop: pushad + int 13h + popad + jnc writestr.return + dec bp + jnz .loop +.disk_error: + jmp strict near kaboom ; Patched + ; -; getonesec: like getlinsec, but pre-sets the count to 1 +; getonesec: get one disk sector ; getonesec: - mov bp,1 - ; Fall through to getlinsec + mov bp,1 ; One sector + ; Fall through ; ; getlinsec: load a sequence of BP floppy sector given by the linear sector @@ -548,18 +495,58 @@ getonesec: ; On return, BX points to the first byte after the transferred ; block. ; -; The "stupid patch area" gets replaced by the code -; mov bp,1 ; nop ... (BD 01 00 90 90...) when installing with -; the -s option. -; -; This routine assumes CS == DS. +; This routine assumes CS == DS, and trashes most registers. ; ; Stylistic note: use "xchg" instead of "mov" when the source is a register ; that is dead from that point; this saves space. However, please keep ; the order to dst,src to keep things sane. ; getlinsec: - mov esi,[bxSecPerTrack] + jmp strict short getlinsec_cbios ; This is patched + +; +; getlinsec_ebios: +; +; getlinsec implementation for EBIOS (EDD) +; +getlinsec_ebios: + mov si,dapa ; Load up the DAPA + mov [si+4],bx + mov [si+6],es + mov [si+8],eax +.loop: + push bp ; Sectors left + call maxtrans ; Enforce maximum transfer size +.bp_ok: + mov [si+2],bp + mov dl,[DriveNumber] + mov ah,42h ; Extended Read + call xint13 + pop bp + movzx eax,word [si+2] ; Sectors we read + add [si+8],eax ; Advance sector pointer + sub bp,ax ; Sectors left + shl ax,9 ; 512-byte sectors + add [si+4],ax ; Advance buffer pointer + and bp,bp + jnz .loop + mov eax,[si+8] ; Next sector + mov bx,[si+4] ; Buffer pointer + ret + +; +; getlinsec_cbios: +; +; getlinsec implementation for legacy CBIOS +; +getlinsec_cbios: +.loop: + push eax + push bp + push bx + + movzx esi,word [bsSecPerTrack] + movzx edi,word [bsHeads] ; ; Dividing by sectors to get (track,sector): we may have ; up to 2^18 tracks, so we need to use 32-bit arithmetric. @@ -570,95 +557,84 @@ getlinsec: xchg cx,dx ; CX <- sector index (0-based) ; EDX <- 0 ; eax = track # - div dword [bxHeads] ; Convert track to head/cyl + div edi ; Convert track to head/cyl ; ; Now we have AX = cyl, DX = head, CX = sector (0-based), ; BP = sectors to transfer, SI = bsSecPerTrack, ; ES:BX = data target ; -gls_nextchunk: push si ; bsSecPerTrack - push bp ; Sectors to transfer - - ; Important - this gets patched with a call. The call - ; assumes cx, si and bp are set up, and can modify bp - ; and destroy si. Until we have the space to do so, - ; transfer one sector at a time. -gls_set_size: -__BEGIN_STUPID_PATCH_AREA: - mov bp,1 ; 3 bytes, same as a call insn -__END_STUPID_PATCH_AREA: - - push ax ; Cylinder # - push dx ; Head # - - push cx ; Sector # + + call maxtrans ; Enforce maximum transfer size + + ; Must not cross track boundaries, so BP <= SI-CX + sub si,cx + cmp bp,si + jna .bp_ok + mov bp,si +.bp_ok: + shl ah,6 ; Because IBM was STOOPID ; and thought 8 bits were enough ; then thought 10 bits were enough... - pop cx ; Sector # - push cx ; Sector # inc cx ; Sector numbers are 1-based, sigh or cl,ah mov ch,al mov dh,dl - mov dl,[bsDriveNumber] + mov dl,[DriveNumber] xchg ax,bp ; Sector to transfer count - ; (xchg shorter than mov) - mov si,retry_count ; # of times to retry a disk access + mov ah,02h ; Read sectors + call xint13 + movzx ecx,al + shl ax,9 ; Convert sectors in AL to bytes in AX + pop bx + add bx,ax + pop bp + pop eax + add eax,ecx + sub bp,cx + jnz .loop + ret + ; -; Do the disk transfer... save the registers in case we fail :( +; Truncate BP to MaxTransfer ; -disk_try_again: - pusha ; - mov ah,02h ; READ DISK - int 13h - popa ; - jc disk_error -; -; Disk access successful -; - pop cx ; Sector # - mov di,ax ; Reduce sector left count - mul word [bsBytesPerSec] ; Figure out how much to advance ptr - add bx,ax ; Update buffer location - pop dx ; Head # - pop ax ; Cyl # - pop bp ; Sectors left to transfer - pop si ; Number of sectors/track - sub bp,di ; Reduce with # of sectors just read - jz writestr.return ; Done! - add cx,di - cmp cx,si - jb gls_nextchunk - inc dx ; Next track on cyl - cmp dx,[bsHeads] ; Was this the last one? - jb gls_nonewcyl - inc ax ; If so, new cylinder - xor dx,dx ; First head on new cylinder -gls_nonewcyl: sub cx,si ; First sector on new track - jmp short gls_nextchunk +maxtrans: + cmp bp,[MaxTransfer] + jna .ok + mov bp,[MaxTransfer] +.ok: ret +; +; Error message on failure +; bailmsg: db 'Boot failed', 0Dh, 0Ah, 0 -bs_checkpt equ $ ; Must be <= 7DEFh +; +; EBIOS disk address packet +; + align 4, db 0 +dapa: + dw 16 ; Packet size +.count: dw 0 ; Block count +.off: dw 0 ; Offset of buffer +.seg: dw 0 ; Segment of buffer +.lba: dd 0 ; LBA (LSW) + dd 0 ; LBA (MSW) + %if 1 bs_checkpt_off equ ($-$$) %ifndef DEPEND -%if bs_checkpt_off > 1EFh +%if bs_checkpt_off > 1F8h %error "Boot sector overflow" %endif %endif - zb 1EFh-($-$$) + zb 1F8h-($-$$) %endif -bs_magic equ $ ; From here to the magic_len equ - ; must match ldlinux_magic -ldlinux_name: db 'LDLINUX SYS' ; Looks like this in the root dir - dd HEXDATE ; Hopefully unique between compiles - +FirstSector dd 0xDEADBEEF ; Location of sector 1 +MaxTransfer dw 0x007F ; Max transfer size bootsignature dw 0AA55h -magic_len equ $-bs_magic ; ; =========================================================================== @@ -678,19 +654,23 @@ syslinux_banner db 0Dh, 0Ah db version_str, ' ', date, ' ', 0 db 0Dh, 0Ah, 1Ah ; EOF if we "type" this in DOS -ldlinux_magic db 'LDLINUX SYS' + align 8, db 0 +ldlinux_magic dd LDLINUX_MAGIC dd HEXDATE - dw 0AA55h ; -; This area is possibly patched by the installer. It is located -; immediately after the EOF + LDLINUX SYS + 4 bytes + 55 AA + alignment, -; so we can find it algorithmically. +; This area is patched by the installer. It is found by looking for +; LDLINUX_MAGIC, plus 4 bytes. ; - alignb 4, db 0 -MaxTransfer dw 00FFh ; Absolutely maximum transfer size +patch_area: +TotalDwords dw 0 ; Total dwords starting at ldlinux_sys +TotalSectors dw 0 ; Number of sectors minus bootsec, this sec +CheckSum dd 0 ; Checksum starting at ldlinux_sys + ; value = LDLINUX_MAGIC - [sum of dwords] + +; Space for up to 64 sectors, the theoretical maximum +SectorPtrs times 64 dd 0 - align 4 ldlinux_ent: ; ; Note that some BIOSes are buggy and run the boot sector at 07C0:0000 @@ -707,175 +687,72 @@ ldlinux_ent: ; mov si,syslinux_banner call writestr -; -; Remember, the boot sector loaded only the first cluster of LDLINUX.SYS. -; We can really only rely on a single sector having been loaded. Hence -; we should load the FAT into RAM and start chasing pointers... -; - xor ax,ax - cwd - inc dx ; DX:AX <- 64K - div word [bxBytesPerSec] ; sectors/64K - mov si,ax - push es - mov bx,fat_seg ; Load into fat_seg:0000 - mov es,bx - - mov eax,[bsHidden] ; Hidden sectors - add edx,[bxResSectors] - add eax,edx - mov ecx,[bxFATsecs] ; Sectors/FAT -fat_load_loop: - mov ebp,ecx ; Make sure high EBP = 0 - cmp bp,si - jna fat_load - mov bp,si ; A full 64K moby -fat_load: - xor bx,bx ; Offset 0 in the current ES - call getlinsecsr - sub cx,bp - jz fat_load_done ; Last moby? - add eax,ebp ; Advance sector count - mov bx,es ; Next 64K moby - add bx,1000h - mov es,bx - jmp short fat_load_loop -fat_load_done: - pop es ; -; Fine, now we have the FAT in memory. How big is a cluster, really? -; Also figure out how many clusters will fit in an 8K buffer, and how -; many sectors and bytes that is +; Patch disk error handling ; - mov edi,[bxBytesPerSec] ; Used a lot below - mov eax,[SecPerClust] - mov si,ax ; Also used a lot - mul di - mov [ClustSize],eax ; Bytes/cluster - mov bx,ax - mov ax,trackbufsize ; High bit 0 - cwd - div bx - mov [BufSafe],ax ; # of cluster in trackbuf - mul si - mov [BufSafeSec],ax - mul di - mov [BufSafeBytes],ax - add ax,getcbuf ; Size of getcbuf is the same - mov [EndOfGetCBuf],ax ; as for trackbuf -; -; FAT12 or FAT16? This computation is fscking ridiculous... -; - mov eax,[bxSectors] - and ax,ax - jnz have_secs - mov eax,[bsHugeSectors] -have_secs: add eax,[bsHidden] ; These are not included - sub eax,[RootDir] ; Start of root directory - movzx ebx,word [RootDirSize] - sub eax,ebx ; Subtract root directory size - xor edx,edx - div esi ; Convert to clusters - cmp ax,4086 ; FAT12 limit - jna is_fat12 - ; Patch the jump - mov byte [nextcluster+1],nextcluster_fat16-(nextcluster+2) -is_fat12: + mov word [xint13.disk_error+1],do_disk_error-(xint13.disk_error+3) ; -; Patch gls_set_size so we can transfer more than one sector at a time. +; Now we read the rest of LDLINUX.SYS. Don't bother loading the first +; sector again, though. ; - mov byte [gls_set_size],0xe8 ; E8 = CALL NEAR - mov word [gls_set_size+1],do_gls_set_size-(gls_set_size+3) - mov byte [disk_error],0xe8 - mov word [disk_error+1],do_disk_error-(disk_error+3) +load_rest: + mov si,SectorPtrs + mov bx,7C00h+2*SECTOR_SIZE ; Where we start loading + mov cx,[TotalSectors] + +.get_chunk: + jcxz .done + xor bp,bp + lodsd ; First sector of this chunk + + mov edx,eax + +.make_chunk: + inc bp + dec cx + jz .chunk_ready + inc edx ; Next linear sector + cmp [esi],edx ; Does it match + jnz .chunk_ready ; If not, this is it + inc esi ; If so, add sector to chunk + jmp short .make_chunk + +.chunk_ready: + call getlinsecsr + jmp .get_chunk + +.done: ; -; Now we read the rest of LDLINUX.SYS. Don't bother loading the first -; cluster again, though. +; All loaded up, verify that we got what we needed. +; Note: the checksum field is embedded in the checksum region, so +; by the time we get to the end it should all cancel out. ; -load_rest: - mov cx,[ClustSize] - mov bx,ldlinux_sys - add bx,cx - mov si,[RunLinClust] - call nextcluster - xor dx,dx - mov ax,ldlinux_len-1 ; To be on the safe side - add ax,cx - div cx ; the number of clusters - dec ax ; We've already read one - jz all_read_jmp - mov cx,ax - call getfssec +verify_checksum: + mov si,ldlinux_sys + mov cx,[TotalDwords] + mov edx,-LDLINUX_MAGIC +.checksum: + lodsd + sub edx,eax + loop .checksum + + and edx,edx ; Should be zero + jz all_read ; We're cool, go for it! + ; -; All loaded up +; Uh-oh, something went bad... ; -all_read_jmp: - jmp all_read + mov si,checksumerr_msg + call writestr + jmp kaboom + ; ; ----------------------------------------------------------------------------- ; Subroutines that have to be in the first sector ; ----------------------------------------------------------------------------- -; -; getfssec: Get multiple clusters from a file, given the starting cluster. -; -; This routine makes sure the subtransfers do not cross a 64K boundary, -; and will correct the situation if it does, UNLESS *sectors* cross -; 64K boundaries. -; -; ES:BX -> Buffer -; SI -> Starting cluster number (2-based) -; CX -> Cluster count (0FFFFh = until end of file) -; -; Returns CF=1 on EOF -; -getfssec: -.getfragment: xor ebp,ebp ; Fragment sector count - lea eax,[si-2] ; Get 0-based sector address - mul dword [SecPerClust] - add eax,[DataArea] -.getseccnt: ; See if we can read > 1 clust - add bp,[SecPerClust] - dec cx ; Reduce clusters left to find - lea di,[si+1] - call nextcluster - cmc - jc .eof ; At EOF? - jcxz .endfragment ; Or was it the last we wanted? - cmp si,di ; Is file continuous? - je .getseccnt ; Yes, we can get -.endfragment: clc ; Not at EOF -.eof: pushf ; Remember EOF or not - push si - push cx -.getchunk: - push eax - mov ax,es ; Check for 64K boundaries. - shl ax,4 - add ax,bx - xor dx,dx - neg ax - setz dl ; DX <- 1 if full 64K segment - div word [bsBytesPerSec] ; How many sectors fit? - mov si,bp - sub si,ax ; Compute remaining sectors - jbe .lastchunk - mov bp,ax - pop eax - call getlinsecsr - add eax,ebp ; EBP<31:16> == 0 - mov bp,si ; Remaining sector count - jmp short .getchunk -.lastchunk: pop eax - call getlinsec - pop cx - pop si - popf - jcxz .return ; If we hit the count limit - jnc .getfragment ; If we didn't hit EOF -.return: ret ; ; getlinsecsr: save registers, call getlinsec, restore registers @@ -886,83 +763,29 @@ getlinsecsr: pushad ret ; -; nextcluster: Advance a cluster pointer in SI to the next cluster -; pointed at in the FAT tables. CF=0 on return if end of file. -; -nextcluster: - jmp short nextcluster_fat12 ; This gets patched - -nextcluster_fat12: - push bx - push ds - mov bx,fat_seg - mov ds,bx - mov bx,si ; Multiply by 3/2 - shr bx,1 ; CF now set if odd - mov si,[si+bx] - jnc nc_even - shr si,4 ; Needed for odd only -nc_even: - and si,0FFFh - cmp si,0FF0h ; Clears CF if at end of file - pop ds - pop bx -nc_return: ret - -; -; FAT16 decoding routine. Note that a 16-bit FAT can be up to 128K, -; so we have to decide if we're in the "low" or the "high" 64K-segment... +; This routine captures disk errors, and tries to decide if it is +; time to reduce the transfer size. ; -nextcluster_fat16: +do_disk_error: + cmp ah,42h + je .ebios + shr al,1 ; Try reducing the transfer size + mov [MaxTransfer],al + jz kaboom ; If we can't, we're dead... + jmp xint13 ; Try again +.ebios: push ax - push ds - mov ax,fat_seg - shl si,1 - jnc .seg0 - mov ax,fat_seg+1000h -.seg0: mov ds,ax - mov si,[si] - cmp si,0FFF0h - pop ds + mov ax,[si+2] + shr ax,1 + mov [MaxTransfer],ax + mov [si+2],ax pop ax - ret + jmp xint13 ; -; Routine that controls how much we can transfer in one chunk. Called -; from gls_set_size in getlinsec. +; Checksum error message ; -do_gls_set_size: - sub si,cx ; Sectors left on track - cmp bp,si - jna .lastchunk - mov bp,si ; No more than a trackful, please! -.lastchunk: - cmp bp,[MaxTransfer] ; Absolute maximum transfer size - jna .oktransfer - mov bp,[MaxTransfer] -.oktransfer: - ret - -; -; This routine captures disk errors, and tries to decide if it is -; time to reduce the transfer size. -; -do_disk_error: - dec si ; Decrement the retry counter - jz kaboom ; If expired, croak - cmp si,2 ; If only 2 attempts left - ja .nodanger - mov al,1 ; Drop transfer size to 1 - jmp short .setsize -.nodanger: - cmp si,retry_count-2 - ja .again ; First time, just try again - shr al,1 ; Otherwise, try to reduce - adc al,0 ; the max transfer size, but not to 0 -.setsize: - mov [MaxTransfer],al -.again: - ret +checksumerr_msg db 'Load error - ', 0 ; Boot failed appended ; ; Debug routine @@ -977,7 +800,7 @@ safedumpregs: rl_checkpt equ $ ; Must be <= 8000h rl_checkpt_off equ ($-$$) -%ifndef DEPEND +%if 0 ; ndef DEPEND %if rl_checkpt_off > 400h %error "Sector 1 overflow" %endif @@ -995,6 +818,70 @@ all_read: mov si,copyright_str call writestr + +; +; Insane hack to expand the superblock to dwords +; +expand_super: + xor eax,eax + mov es,ax ; INT 13:08 destroys ES + mov si,superblock + mov di,SuperInfo + mov cx,superinfo_size +.loop: + lodsw + dec si + stosd ; Store expanded word + xor ah,ah + stosd ; Store expanded byte + +; +; How big is a cluster, really? Also figure out how many clusters +; will fit in an 8K buffer, and how many sectors and bytes that is +; + mov edi,[bxBytesPerSec] ; Used a lot below + mov eax,[SecPerClust] + mov si,ax ; Also used a lot + mul di + mov [ClustSize],eax ; Bytes/cluster + mov bx,ax + mov ax,trackbufsize ; High bit 0 + cwd + div bx + mov [BufSafe],ax ; # of cluster in trackbuf + mul si + mov [BufSafeSec],ax + mul di + mov [BufSafeBytes],ax + add ax,getcbuf ; Size of getcbuf is the same + mov [EndOfGetCBuf],ax ; as for trackbuf +; +; FAT12, FAT16 or FAT28^H^H32? This computation is fscking ridiculous... +; +getfattype: + mov eax,[bxSectors] + and ax,ax + jnz .have_secs + mov eax,[bsHugeSectors] +.have_secs: add eax,[bsHidden] ; These are not included + sub eax,[RootDir] ; Start of root directory + movzx ebx,word [RootDirSize] + sub eax,ebx ; Subtract root directory size + xor edx,edx + div esi ; Convert to clusters + mov cl,nextcluster_fat12-(nextcluster+2) + cmp eax,4086 ; FAT12 limit + jna .setsize + mov cl,nextcluster_fat16-(nextcluster+2) + cmp eax,65526 ; FAT16 limit + jna .setsize + mov cl,nextcluster_fat28-(nextcluster+2) +.setsize: + mov byte [nextcluster+1],cl + + + + ; ; Common initialization code ; @@ -1342,6 +1229,209 @@ lc_1: cmp al,lcase_low pop bx lc_ret: ret +; +; getfssec: Get multiple sectors from a file +; +; This routine makes sure the subtransfers do not cross a 64K boundary, +; and will correct the situation if it does, UNLESS *sectors* cross +; 64K boundaries. +; +; ES:BX -> Buffer +; SI -> Pointer to structure: +; 0 - dword - Starting cluster number (2-based) +; 4 - word - Sector number within cluster +; 8 - dword - Absolute sector number +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns CF=1 on EOF +; +getfssec: +.getfragment: xor ebp,ebp ; Fragment sector count + mov edi,[si] + lea eax,[edi-2] + ; mov eax,ebx + ; sub eax,2 + ; jc .isrootdir ; Use cluster 1 for the root directory + mul dword [SecPerClust] + add eax,[DataArea] + sub bp,[si+4] ; Sectors already read +.getseccnt: ; See if we can read > 1 clust + add bp,[SecPerClust] + cmp cx,bp + jna .endfragment ; Done? + lea eax,[edi+1] + call nextcluster + jc .eof ; At EOF? + cmp eax,edi ; Is file continuous? + je .getseccnt ; Yes, we can get +.endfragment: clc ; Not at EOF +.eof: pushf ; Remember EOF or not + push si + push cx +.getchunk: + push eax + mov ax,es ; Check for 64K boundaries. + shl ax,4 + add ax,bx + xor dx,dx + neg ax + setz dl ; DX <- 1 if full 64K segment + div word [bsBytesPerSec] ; How many sectors fit? + mov si,bp + sub si,ax ; Compute remaining sectors + jbe .lastchunk + mov bp,ax + pop eax + call getlinsecsr + add eax,ebp ; EBP<31:16> == 0 + mov bp,si ; Remaining sector count + jmp short .getchunk +.lastchunk: pop eax + call getlinsec + pop cx + pop si + popf + jcxz .return ; If we hit the count limit + jnc .getfragment ; If we didn't hit EOF +.return: ret + +; +; nextcluster: Advance a cluster pointer in EDI to the next cluster +; pointed at in the FAT tables. CF=0 on return if end of file. +; +nextcluster: + jmp strict short nextcluster_fat28 ; This gets patched + +nextcluster_fat12: + push eax + push edx + push bx + push cx + push si + mov edx,edi + shr edi,1 + add edx,edi + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov cl,[gs:si+bx] + inc edx + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov ch,[gs:si+bx] + test di,1 + jz .even + shr cx,4 +.even: and cx,0FFFh + movzx edi,cx + cmp di,0FF0h + pop si + pop cx + pop bx + pop edx + pop eax + ret + +; +; FAT16 decoding routine. +; +nextcluster_fat16: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-1 + call getfatsector + mov bx,di + add bx,bx + and bx,1FEh + movzx edi,word [gs:si+bx] + cmp di,0FFF0h + pop bx + pop si + pop eax + ret +; +; FAT28 ("FAT32") decoding routine. +; +nextcluster_fat28: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-2 + call getfatsector + mov bx,di + add bx,bx + add bx,bx + and bx,1FCh + mov edi,dword [gs:si+bx] + and edi,0FFFFFFFh ; 28 bits only + cmp edi,0FFFFFF0h + pop bx + pop si + pop eax + ret + +; +; getfatsector: Check for a particular sector (in EAX) in the FAT cache, +; and return a pointer in GS:SI, loading it if needed. +; +; Assumes CS == DS. +; +getfatsector: + add eax,[bsHidden] ; Hidden sectors + add eax,[bxResSectors] ; Reserved sectors + ; Fall through + +; +; getcachesector: Check for a particular sector (EAX) in the sector cache, +; and if it is already there, return a pointer in GS:SI +; otherwise load it and return said pointer. +; +; Assumes CS == DS. +; +getcachesector: + push cx + mov si,cache_seg + mov gs,si + mov si,CachePtrs ; Sector cache pointers + mov cx,65536/SECTOR_SIZE + repne scasd ; Do we have it? + jne .miss + ; We have it; get the pointer + sub si,CachePtrs+4 + shl si,SECTOR_SHIFT-2 + pop cx + ret +.miss: + ; Need to load it. Highly inefficient cache replacement + ; algorithm: Least Recently Written (LRW) + push bx + push es + push gs + pop es + mov bx,[NextCacheSlot] + inc bx + and bx,(1 << (16-SECTOR_SHIFT))-1 + mov [NextCacheSlot],bx + shl bx,2 + mov [CachePtrs+bx],eax + shl bx,SECTOR_SHIFT-2 + mov si,bx + pushad + call getonesec + popad + pop es + pop bx + pop cx + ret + ; ----------------------------------------------------------------------------- ; Common modules ; ----------------------------------------------------------------------------- diff --git a/libfat/cache.c b/libfat/cache.c new file mode 100644 index 0000000..cc6c57f --- /dev/null +++ b/libfat/cache.c @@ -0,0 +1,70 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * cache.c + * + * Simple sector cache + */ + +#include +#include "libfatint.h" + +void * libfat_get_sector(struct libfat_filesystem *fs, libfat_sector_t n) +{ + struct libfat_sector *ls; + + for ( ls = fs->sectors ; ls ; ls = ls->next ) { + if ( ls->n == n ) + return ls->data; /* Found in cache */ + } + + /* Not found in cache */ + ls = malloc(sizeof(struct libfat_sector)); + if ( !ls ) { + libfat_flush(fs); + ls = malloc(sizeof(struct libfat_sector)); + + if ( !ls ) + return NULL; /* Can't allocate memory */ + } + + if ( fs->read(fs->readptr, ls->data, LIBFAT_SECTOR_SIZE, n) + != LIBFAT_SECTOR_SIZE ) { + free(ls); + return NULL; /* I/O error */ + } + + ls->n = n; + ls->next = fs->sectors; + fs->sectors = ls; + + return ls->data; +} + +void libfat_flush(struct libfat_filesystem *fs) +{ + struct libfat_sector *ls, *lsnext; + + lsnext = fs->sectors; + fs->sectors = NULL; + + for ( ls = lsnext ; ls ; ls = lsnext ) { + lsnext = ls->next; + free(ls); + } +} + + + + diff --git a/libfat/fat.h b/libfat/fat.h new file mode 100644 index 0000000..921f9f4 --- /dev/null +++ b/libfat/fat.h @@ -0,0 +1,112 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * fat.h + * + * Basic data structures for a FAT filesystem + */ + +#ifndef FAT_H +#define FAT_H + +#include "ulint.h" + +/* The poor excuse FAT has for a superblock -- in the boot sector */ +struct fat_bootsect { + le8_t bsJump[3]; /* Jump to code */ + char bsOemName[8]; /* Formatting program */ + le16_t bsBytesPerSec; /* Bytes/sector */ + le8_t bsSecPerClust; /* Sectors/cluster */ + le16_t bsResSectors; /* Reserved sectors */ + le8_t bsFATs; /* Number of FATs */ + le16_t bsRootDirEnts; /* Number of entries/root directory */ + le16_t bsSectors; /* Number of sectors [1] */ + le8_t bsMedia; /* Magic media type byte */ + le16_t bsFATsecs; /* Sectors/FAT */ + le16_t bsSecPerTrack; /* Sectors/track */ + le16_t bsHeads; /* Number of heads */ + le32_t bsHiddenSecs; /* Number of hidden sectors */ + le32_t bsHugeSectors; /* Number of sectors [2] */ + union { + /* FAT12/16 */ + struct { + le8_t bsDriveNumber; /* Drive number */ + le8_t bsReserved1; /* Reserved */ + le8_t bsBootSignature; /* 0x29 */ + le32_t bsVolumeID; /* Volume serial number */ + char bsVolumeLabel[11]; /* Volume name */ + char bsFileSysType[8]; /* File system type */ + + le8_t bsCode[448]; /* Boot sector code */ + } fat16; + + /* FAT32 */ + struct { + le32_t bpb_fatsz32; /* Sectors/FAT */ + le16_t bpb_extflags; /* Extended flags */ + le16_t bpb_fsver; /* Filesystem version */ + le32_t bpb_rootclus; /* Root directory cluster */ + le16_t bpb_fsinfo; /* FSINFO sector number */ + le16_t bpb_bkbootsec; /* Backup boot sector (superblock) */ + char bpb_reserved[12]; + + /* Same shit, different offset! */ + le8_t bsDriveNumber; /* Drive number */ + le8_t bsReserved1; /* Reserved */ + le8_t bsBootSignature; /* 0x29 */ + le32_t bsVolumeID; /* Volume serial number */ + char bsVolumeLabel[11]; /* Volume name */ + char bsFileSysType[8]; /* File system type */ + + le8_t bsCode[420]; /* Boot sector code */ + } fat32; + } u; + + le16_t bsSignature; /* 0xAA55 */ +}; + +#define BS_BOOTSIGNATURE 0x29 +#define BS_SIGNATURE 0xAA55 + +/* A FAT filesystem directory entry */ + +struct fat_dirent +{ + le8_t name[11]; /* Mangled filename */ + le8_t attribute; /* File type/attribute */ + le8_t caseflags; /* VFAT: case for basis and extension */ + le8_t ctime_ms; /* ms of creation time */ + le32_t ctime; /* Creation time */ + le16_t atime; /* Date portion (high 16 bits) of atime */ + le16_t clusthi; /* FAT32: high 16 bits of cluster */ + le32_t mtime; /* Modification time */ + le16_t clustlo; /* First cluster pointer */ + le32_t size; /* File size (bytes) */ +}; + +/* A VFAT filesystem continuation entry */ +struct fat_vfat_slot +{ + le8_t id; /* Sequence number for slot */ + le16_t name0[5]; /* 5 characters */ + le8_t attribute; /* Attribute byte */ + le8_t reserved; /* Reserved, MBZ */ + le8_t alias_csum; /* Short name checksum */ + le16_t name5[6]; /* 6 characters */ + le16_t firstclust; /* MBZ */ + le16_t name11[2]; /* 2 characters */ +}; + +#endif /* FAT_H */ + diff --git a/libfat/fatchain.c b/libfat/fatchain.c new file mode 100644 index 0000000..6c640be --- /dev/null +++ b/libfat/fatchain.c @@ -0,0 +1,136 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * fatchain.c + * + * Follow a FAT chain + */ + +#include "libfatint.h" +#include "ulint.h" + +/* + * Convert a cluster number (or 0 for the root directory) to a + * sector number. Return -1 on failure. + */ +libfat_sector_t libfat_clustertosector(const struct libfat_filesystem *fs, + int32_t cluster) +{ + if ( cluster == 0 ) + cluster = fs->rootcluster; + + if ( cluster == 0 ) + return fs->rootdir; + else if ( cluster < 2 || cluster >= fs->endcluster ) + return -1; + else + return fs->data + ((libfat_sector_t)(cluster-2) << fs->clustshift); +} + +/* + * Get the next sector of either the root directory or a FAT chain. + * Returns 0 on end of file and -1 on error. + */ + +libfat_sector_t libfat_nextsector(struct libfat_filesystem *fs, + libfat_sector_t s) +{ + int32_t cluster, nextcluster; + uint32_t fatoffset; + libfat_sector_t fatsect; + uint8_t *fsdata; + uint32_t clustmask = fs->clustsize - 1; + libfat_sector_t rs; + + if ( s < fs->data ) { + if ( s < fs->rootdir ) + return -1; + + /* Root directory */ + s++; + return ( s < fs->data ) ? s : 0; + } + + rs = s - fs->data; + + if ( ~rs & clustmask ) + return s+1; /* Next sector in cluster */ + + cluster = 2 + (rs >> fs->clustshift); + + if ( cluster >= fs->endcluster ) + return -1; + + switch ( fs->fat_type ) { + case FAT12: + /* Get first byte */ + fatoffset = cluster + (cluster >> 1); + fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT); + fsdata = libfat_get_sector(fs, fatsect); + if ( !fsdata ) + return -1; + nextcluster = fsdata[fatoffset & LIBFAT_SECTOR_MASK]; + + /* Get second byte */ + fatoffset++; + fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT); + fsdata = libfat_get_sector(fs, fatsect); + if ( !fsdata ) + return -1; + nextcluster |= fsdata[fatoffset & LIBFAT_SECTOR_MASK] << 8; + + /* Extract the FAT entry */ + if ( cluster & 1 ) + nextcluster >>= 4; + else + nextcluster &= 0x0FFF; + + if ( nextcluster >= 0x0FF8 ) + return 0; + break; + + case FAT16: + fatoffset = cluster << 1; + fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT); + fsdata = libfat_get_sector(fs, fatsect); + if ( !fsdata ) + return -1; + nextcluster = read16((le16_t *)&fsdata[fatoffset & LIBFAT_SECTOR_MASK]); + + if ( nextcluster >= 0x0FFF8 ) + return 0; + break; + + case FAT28: + fatoffset = cluster << 2; + fatsect = fs->fat + (fatoffset >> LIBFAT_SECTOR_SHIFT); + fsdata = libfat_get_sector(fs, fatsect); + if ( !fsdata ) + return -1; + nextcluster = read32((le32_t *)&fsdata[fatoffset & LIBFAT_SECTOR_MASK]); + nextcluster &= 0x0FFFFFFF; + + if ( nextcluster >= 0x0FFFFFF8 ) + return 0; + break; + + default: + return -1; /* WTF? */ + } + + return libfat_clustertosector(fs, nextcluster); +} + + + diff --git a/libfat/libfat.h b/libfat/libfat.h new file mode 100644 index 0000000..b068227 --- /dev/null +++ b/libfat/libfat.h @@ -0,0 +1,81 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * libfat.h + * + * Headers for the libfat library + */ + +#ifndef LIBFAT_H +#define LIBFAT_H + +#include +#include + +#define LIBFAT_SECTOR_SHIFT 9 +#define LIBFAT_SECTOR_SIZE 512 +#define LIBFAT_SECTOR_MASK 511 + +typedef uint32_t libfat_sector_t; +struct libfat_filesystem; + +/* + * Open the filesystem. The readfunc is the function to read + * sectors, in the format: + * int readfunc(void *readptr, void *buf, size_t secsize, + * libfat_sector_t secno) + * + * ... where readptr is a private argument. + * + * A return value of != secsize is treated as error. + */ +struct libfat_filesystem * +libfat_open(int (*readfunc)(void *, void *, size_t, libfat_sector_t), + void *readptr); + +void libfat_close(struct libfat_filesystem *); + +/* + * Convert a cluster number (or 0 for the root directory) to a + * sector number. Return -1 on failure. + */ +libfat_sector_t libfat_clustertosector(const struct libfat_filesystem *fs, + int32_t cluster); + +/* + * Get the next sector of either the root directory or a FAT chain. + * Returns 0 on end of file and -1 on error. + */ +libfat_sector_t libfat_nextsector(struct libfat_filesystem *fs, + libfat_sector_t s); + +/* + * Flush all cached sectors for this filesystem. + */ +void libfat_flush(struct libfat_filesystem *fs); + +/* + * Get a pointer to a specific sector. + */ +void * libfat_get_sector(struct libfat_filesystem *fs, libfat_sector_t n); + +/* + * Search a FAT directory for a particular pre-mangled filename. + * Copies the directory entry into direntry and returns 0 if found. + */ +int32_t libfat_searchdir(struct libfat_filesystem *fs, int32_t dirclust, + const void *name, void *direntry); + +#endif /* LIBFAT_H */ + diff --git a/libfat/libfatint.h b/libfat/libfatint.h new file mode 100644 index 0000000..6da47f1 --- /dev/null +++ b/libfat/libfatint.h @@ -0,0 +1,56 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * libfatint.h + * + * Internals for the libfat filesystem + */ + +#ifndef LIBFATINT_H +#define LIBFATINT_H + +#include "libfat.h" +#include "fat.h" + +struct libfat_sector { + libfat_sector_t n; /* Sector number */ + struct libfat_sector *next; /* Next in list */ + char data[LIBFAT_SECTOR_SIZE]; +}; + +enum fat_type { + FAT12, + FAT16, + FAT28 +}; + +struct libfat_filesystem { + int (*read)(void *, void *, size_t, libfat_sector_t); + void *readptr; + + enum fat_type fat_type; + unsigned int clustsize; + int clustshift; + int32_t endcluster; /* Highest legal cluster number + 1 */ + int32_t rootcluster; /* Root directory cluster */ + + libfat_sector_t fat; /* Start of FAT */ + libfat_sector_t rootdir; /* Start of root directory */ + libfat_sector_t data; /* Start of data area */ + libfat_sector_t end; /* End of filesystem */ + + struct libfat_sector *sectors; +}; + +#endif /* LIBFATINT_H */ diff --git a/libfat/open.c b/libfat/open.c new file mode 100644 index 0000000..835c336 --- /dev/null +++ b/libfat/open.c @@ -0,0 +1,118 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * open.c + * + * Open a FAT filesystem and compute some initial values; return NULL + * on failure. + */ + +#include +#include "libfatint.h" +#include "ulint.h" + +struct libfat_filesystem * +libfat_open(int (*readfunc)(void *, void *, size_t, libfat_sector_t), + void *readptr) +{ + struct libfat_filesystem *fs = NULL; + struct fat_bootsect *bs; + int i; + uint32_t sectors, fatsize, minfatsize, rootdirsize; + uint32_t nclusters; + + fs = malloc(sizeof(struct libfat_filesystem)); + if ( !fs ) + goto barf; + + fs->sectors = NULL; + fs->read = readfunc; + fs->readptr = readptr; + + bs = libfat_get_sector(fs, 0); + if ( !bs ) + goto barf; + + if ( read16(&bs->bsBytesPerSec) != LIBFAT_SECTOR_SIZE ) + goto barf; + + for ( i = 0 ; i <= 8 ; i++ ) { + if ( (uint8_t)(1 << i) == read8(&bs->bsSecPerClust) ) + break; + } + if ( i > 8 ) + goto barf; + fs->clustsize = 1 << i; /* Treat 0 as 2^8 = 64K */ + fs->clustshift = i; + + sectors = read16(&bs->bsSectors); + if ( !sectors ) + sectors = read32(&bs->bsHugeSectors); + + fs->end = sectors; + + fs->fat = read16(&bs->bsResSectors); + fatsize = read16(&bs->bsFATsecs); + if ( !fatsize ) + fatsize = read32(&bs->u.fat32.bpb_fatsz32); + + fs->rootdir = fs->fat + fatsize * read8(&bs->bsFATs); + + rootdirsize = ((read16(&bs->bsRootDirEnts) << 5) + LIBFAT_SECTOR_MASK) + >> LIBFAT_SECTOR_SHIFT; + fs->data = fs->rootdir + rootdirsize; + + /* Sanity checking */ + if ( fs->data >= fs->end ) + goto barf; + + /* Figure out how many clusters */ + nclusters = (fs->end - fs->data) >> fs->clustshift; + fs->endcluster = nclusters + 2; + + if ( nclusters <= 0xff4 ) { + fs->fat_type = FAT12; + minfatsize = fs->endcluster + (fs->endcluster >> 1); + } else if ( nclusters <= 0xfff4 ) { + fs->fat_type = FAT16; + minfatsize = fs->endcluster << 1; + } else if ( nclusters <= 0xffffff4 ) { + fs->fat_type = FAT28; + minfatsize = fs->endcluster << 2; + } else + goto barf; /* Impossibly many clusters */ + + minfatsize = (minfatsize + LIBFAT_SECTOR_SIZE-1) >> LIBFAT_SECTOR_SHIFT; + + if ( minfatsize > fatsize ) + goto barf; /* The FATs don't fit */ + + if ( fs->fat_type == FAT28 ) + fs->rootcluster = read32(&bs->u.fat32.bpb_rootclus); + else + fs->rootcluster = 0; + + return fs; /* All good */ + + barf: + if ( fs ) + free(fs); + return NULL; +} + +void libfat_close(struct libfat_filesystem *fs) +{ + libfat_flush(fs); + free(fs); +} diff --git a/libfat/searchdir.c b/libfat/searchdir.c new file mode 100644 index 0000000..7c07165 --- /dev/null +++ b/libfat/searchdir.c @@ -0,0 +1,61 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * searchdir.c + * + * Search a FAT directory for a particular pre-mangled filename. + * Copies the directory entry into direntry and returns the starting cluster + * if found; returns -2 on not found, -1 on error, 0 on empty file. + */ + +#include +#include "libfatint.h" + +int32_t libfat_searchdir(struct libfat_filesystem *fs, int32_t dirclust, + const void *name, void *direntry) +{ + struct fat_dirent *dep; + int nent; + libfat_sector_t s = libfat_clustertosector(fs, dirclust); + + while ( 1 ) { + if ( s == 0 ) + return -2; /* Not found */ + else if ( s == (libfat_sector_t)-1 ) + return -1; /* Error */ + + dep = libfat_get_sector(fs, s); + if ( !dep ) + return -1; /* Read error */ + + for ( nent = LIBFAT_SECTOR_SIZE/sizeof(struct fat_dirent) ; + nent ; nent-- ) { + if ( !memcmp(dep->name, name, 11) ) { + if ( direntry ) + memcpy(direntry, dep, sizeof (*dep)); + if ( read32(&dep->size) == 0 ) + return 0; /* An empty file has no clusters */ + else + return read16(&dep->clustlo) + (read16(&dep->clusthi) << 16); + } + + if ( dep->name[0] == 0 ) + return -2; /* Hit high water mark */ + + dep++; + } + + s = libfat_nextsector(fs, s); + } +} diff --git a/libfat/ulint.h b/libfat/ulint.h new file mode 100644 index 0000000..0b200ea --- /dev/null +++ b/libfat/ulint.h @@ -0,0 +1,115 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139, + * USA; either version 2 of the License, or (at your option) any later + * version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * ulint.h + * + * Basic operations on unaligned, littleendian integers + */ + +#ifndef ULINT_H +#define ULINT_H + +#include + +/* These are unaligned, littleendian integer types */ + +typedef uint8_t le8_t; /* 8-bit byte */ +typedef uint8_t le16_t[2]; /* 16-bit word */ +typedef uint8_t le32_t[4]; /* 32-bit dword */ + +/* Read/write these quantities */ + +static inline unsigned char +read8(le8_t *_p) +{ + return *_p; +} + +static inline void +write8(le8_t *_p, unsigned char _v) +{ + *_p = _v; +} + +#if defined(__i386__) || defined(__x86_64__) + +/* Littleendian architectures which support unaligned memory accesses */ + +static inline unsigned short +read16(le16_t *_p) +{ + return *((unsigned short *)_p); +} + +static inline void +write16(le16_t *_p, unsigned short _v) +{ + *((unsigned short *)_p) = _v; +} + +static inline unsigned int +read32(le32_t *_p) +{ + return *((unsigned int *)_p); +} + +static inline void +write32(le32_t *_p, unsigned int _v) +{ + *((unsigned int *)_p) = _v; +} + +#else + +/* Generic, mostly portable versions */ + +static inline unsigned short +read16(le16_t *_p) +{ + unsigned short _v; + + _v = p[0]; + _v |= p[1] << 8; + return _v; +} + +static inline void +write16(le16_t *_p, unsigned short _v) +{ + _p[0] = _v & 0xFF; + _p[1] = (_v >> 8) & 0xFF; +} + +static inline unsigned int +read32(le32_t *_p) +{ + _v = _p[0]; + _v |= _p[1] << 8; + _v |= _p[2] << 16; + _v |= _p[3] << 24; + return _v; +} + +static inline void +write32(le32_t *_p, unsigned int _v) +{ + _p[0] = _v & 0xFF; + _p[1] = (_v >> 8) & 0xFF; + _p[2] = (_v >> 16) & 0xFF; + _p[3] = (_v >> 24) & 0xFF; +} + +#endif + +#endif /* ULINT_H */ diff --git a/mtools/Makefile b/mtools/Makefile new file mode 100644 index 0000000..a13642c --- /dev/null +++ b/mtools/Makefile @@ -0,0 +1,39 @@ +CC = gcc +OPTFLAGS = -g -O -Dinline= +INCLUDES = -I. -I.. -I../libfat +CFLAGS = -W -Wall -D_FILE_OFFSET_BITS=64 $(OPTFLAGS) $(INCLUDES) +LDFLAGS = + +SRCS = syslinux.c ../syslxmod.c ../bootsect_bin.c ../ldlinux_bin.c $(wildcard ../libfat/*.c) +OBJS = $(patsubst %.c,%.o,$(notdir $(SRCS))) + +.SUFFIXES: .c .o .i .s .S + +VPATH = .:..:../libfat + +all: installer + +tidy: + -rm -f *.o *.i *.s *.a .*.d + +clean: tidy + -rm -f syslinux + +spotless: clean + +installer: syslinux + +syslinux: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) -Wp,-MT,$@,-MMD,.$@.d $(CFLAGS) -c -o $@ $< +%.i: %.c + $(CC) $(CFLAGS) -E -o $@ $< +%.s: %.c + $(CC) $(CFLAGS) -S -o $@ $< + +-include .*.d + + + diff --git a/syslinux.c b/mtools/syslinux.c similarity index 80% rename from syslinux.c rename to mtools/syslinux.c index bb1de8a..69d7a3d 100644 --- a/syslinux.c +++ b/mtools/syslinux.c @@ -37,6 +37,7 @@ #include #include "syslinux.h" +#include "libfat.h" char *program; /* Name of program */ char *device; /* Device to install to */ @@ -109,6 +110,16 @@ ssize_t xpwrite(int fd, void *buf, size_t count, off_t offset) return done; } +/* + * Version of the read function suitable for libfat + */ +int libfat_xpread(void *pp, void *buf, size_t secsize, libfat_sector_t sector) +{ + off_t offset = (off_t)sector * secsize; + return xpread((int)pp, buf, secsize, offset); +} + + int main(int argc, char *argv[]) { static unsigned char sectbuf[512]; @@ -121,6 +132,12 @@ int main(int argc, char *argv[]) char mtools_conf[] = "/tmp/syslinux-mtools-XXXXXX"; int mtc_fd; FILE *mtc, *mtp; + struct libfat_filesystem *fs; + libfat_sector_t s, *secp, sectors[65]; /* 65 is maximum possible */ + int32_t ldlinux_cluster; + int nsectors; + + (void)argc; /* Unused */ mypid = getpid(); program = argv[0]; @@ -139,7 +156,7 @@ int main(int argc, char *argv[]) } else if ( *opt == 'f' ) { force = 1; /* Force install */ } else if ( *opt == 'o' && argp[1] ) { - offset = strtoul(*++argp, NULL, 0); /* Byte offset */ + offset = (off_t)strtoull(*++argp, NULL, 0); /* Byte offset */ } else { usage(); } @@ -170,17 +187,12 @@ int main(int argc, char *argv[]) exit(1); } - if ( !force && offset != 0 && !S_ISREG(st.st_mode) ) { - fprintf(stderr, "%s: not a regular file and an offset specified (use -f to override)\n", device); - exit(1); - } - xpread(dev_fd, sectbuf, 512, offset); /* * Check to see that what we got was indeed an MS-DOS boot sector/superblock */ - if(!syslinux_check_bootsect(sectbuf,device)) { + if( !syslinux_check_bootsect(sectbuf,device) ) { exit(1); } @@ -197,7 +209,7 @@ int main(int argc, char *argv[]) "MTOOLS_SKIP_CHECK=1\n" /* Needed for some flash memories */ "drive s:\n" " file=\"/proc/%lu/fd/%d\"\n" - " offset=%lld\n", + " offset=%llu\n", (unsigned long)mypid, dev_fd, (unsigned long long)offset); @@ -223,17 +235,42 @@ int main(int argc, char *argv[]) exit(1); } - status = system("mattrib +r s:ldlinux.sys"); + status = system("mattrib +r +h +s s:ldlinux.sys"); if ( !WIFEXITED(status) || WEXITSTATUS(status) ) { fprintf(stderr, - "%s: warning: failed to set readonly bit on ldlinux.sys\n", + "%s: warning: failed to set system bit on ldlinux.sys\n", program); } unlink(mtools_conf); /* + * Now, use libfat to create a block map + */ + fs = libfat_open(libfat_xpread, (void *)dev_fd); + ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL); + secp = sectors; + nsectors = 0; + s = libfat_clustertosector(fs, ldlinux_cluster); + while ( s && nsectors < 65 ) { + *secp++ = s; + nsectors++; + s = libfat_nextsector(fs, s); + } + libfat_close(fs); + + /* + * Patch ldlinux.sys and the boot sector + */ + syslinux_patch(sectors, nsectors); + + /* + * Write the now-patched first sector of ldlinux.sys + */ + xpwrite(dev_fd, syslinux_ldlinux, 512, offset + ((off_t)sectors[0] << 9)); + + /* * To finish up, write the boot sector */ diff --git a/syslinux.h b/syslinux.h index f20172c..da6eecc 100644 --- a/syslinux.h +++ b/syslinux.h @@ -23,7 +23,7 @@ extern unsigned char syslinux_ldlinux[]; extern unsigned int syslinux_ldlinux_len; extern int syslinux_ldlinux_mtime; -/* This switches the boot sector and ldlinux to "stupid mode" */ +/* This switches the boot sector to "stupid mode" */ void syslinux_make_stupid(void); /* This takes a boot sector and merges in the syslinux fields */ @@ -32,4 +32,7 @@ void syslinux_make_bootsect(void *); /* Check to see that what we got was indeed an MS-DOS boot sector/superblock */ int syslinux_check_bootsect(const void *bs, const char *device); +/* This patches the boot sector and ldlinux.sys based on a sector map */ +int syslinux_patch(const uint32_t *sectors, int nsectors); + #endif diff --git a/syslxmod.c b/syslxmod.c index 96e6088..1f83743 100644 --- a/syslxmod.c +++ b/syslxmod.c @@ -23,6 +23,8 @@ #include "syslinux.h" +#define LDLINUX_MAGIC 0x3eb202fe + enum bs_offsets { bsJump = 0x00, bsOemName = 0x03, @@ -49,7 +51,7 @@ enum bs_offsets { }; #define bsHead bsJump -#define bsHeadLen (bsBytesPerSec-bsHead) +#define bsHeadLen (bsOemName-bsHead) #define bsCodeLen (bsSignature-bsCode) /* @@ -78,25 +80,33 @@ static inline uint32_t get_32(const unsigned char *p) static inline void set_16(unsigned char *p, uint16_t v) { +#if defined(__i386__) || defined(__x86_64__) + /* Littleendian and unaligned-capable */ + *(uint16_t *)p = v; +#else p[0] = (v & 0xff); p[1] = ((v >> 8) & 0xff); +#endif } -#if 0 /* Not needed */ static inline void set_32(unsigned char *p, uint32_t v) { +#if defined(__i386__) || defined(__x86_64__) + /* Littleendian and unaligned-capable */ + *(uint32_t *)p = v; +#else p[0] = (v & 0xff); p[1] = ((v >> 8) & 0xff); p[2] = ((v >> 16) & 0xff); p[3] = ((v >> 24) & 0xff); -} #endif +} /* Patch the code so that we're running in stupid mode */ void syslinux_make_stupid(void) { /* Access only one sector at a time */ - set_16(syslinux_ldlinux+PATCH_OFFSET, 1); + set_16(syslinux_bootsect+0x1FC, 1); } void syslinux_make_bootsect(void *bs) @@ -108,7 +118,8 @@ void syslinux_make_bootsect(void *bs) } /* - * Check to see that what we got was indeed an MS-DOS boot sector/superblock + * Check to see that what we got was indeed an MS-DOS boot sector/superblock; + * Return 0 if bad and 1 if OK. */ int syslinux_check_bootsect(const void *bs, const char *device) { @@ -116,6 +127,8 @@ int syslinux_check_bootsect(const void *bs, const char *device) unsigned int sectors, clusters; const unsigned char *sectbuf = bs; + /*** FIX: Handle FAT32 ***/ + if ( sectbuf[bsBootSignature] == 0x29 ) { /* It's DOS, and it has all the new nice fields */ @@ -164,11 +177,60 @@ int syslinux_check_bootsect(const void *bs, const char *device) return 0; } - if ( sectbuf[bsSecPerClust] > 32 ) { - fprintf(stderr, "%s: Cluster sizes larger than 16K not supported\n", - device); - return 0; + return 1; +} + +/* + * This patches the boot sector and the first sector of ldlinux.sys + * based on an ldlinux.sys sector map passed in. Typically this is + * handled by writing ldlinux.sys, mapping it, and then overwrite it + * with the patched version. If this isn't safe to do because of + * an OS which does block reallocation, then overwrite it with + * direct access since the location is known. + * + * Return 0 if successful, otherwise -1. + */ +int syslinux_patch(const uint32_t *sectors, int nsectors) +{ + unsigned char *patcharea, *p; + int nsect = (syslinux_ldlinux_len+511) >> 9; + uint32_t csum; + int i, dw; + + if ( nsectors < nsect ) + return -1; + + /* First sector need pointer in boot sector */ + set_32(syslinux_bootsect+0x1F8, *sectors++); + nsect--; + + /* Search for LDLINUX_MAGIC to find the patch area */ + for ( p = syslinux_ldlinux ; get_32(p) != LDLINUX_MAGIC ; p += 4 ); + patcharea = p+4; + + /* Set up the totals */ + dw = syslinux_ldlinux_len >> 2; /* COMPLETE dwords! */ + set_16(patcharea, dw); + set_16(patcharea+2, nsect); /* Does not include the first sector! */ + + /* Set the sector pointers */ + p = patcharea+8; + + memset(p, 0, 64*4); + while ( nsect-- ) { + set_32(p, *sectors++); + p += 4; } - return 1; + /* Now produce a checksum */ + set_32(patcharea+4, 0); + + csum = LDLINUX_MAGIC; + for ( i = 0, p = syslinux_ldlinux ; i < dw ; i++, p += 4 ) + csum -= get_32(p); /* Negative checksum */ + + set_32(patcharea+4, csum); + + return 0; } + diff --git a/unix/Makefile b/unix/Makefile new file mode 100644 index 0000000..9ba5e55 --- /dev/null +++ b/unix/Makefile @@ -0,0 +1,39 @@ +CC = gcc +OPTFLAGS = -g -O +INCLUDES = -I. -I.. -I../libfat +CFLAGS = -W -Wall -D_FILE_OFFSET_BITS=64 $(OPTFLAGS) $(INCLUDES) +LDFLAGS = + +SRCS = syslinux.c ../syslxmod.c ../bootsect_bin.c ../ldlinux_bin.c $(wildcard ../libfat/*.c) +OBJS = $(patsubst %.c,%.o,$(notdir $(SRCS))) + +.SUFFIXES: .c .o .i .s .S + +VPATH = .:..:../libfat + +all: installer + +tidy: + -rm -f *.o *.i *.s *.a .*.d + +clean: tidy + -rm -f syslinux + +spotless: clean + +installer: syslinux + +syslinux: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) -Wp,-MT,$@,-MMD,.$@.d $(CFLAGS) -c -o $@ $< +%.i: %.c + $(CC) $(CFLAGS) -E -o $@ $< +%.s: %.c + $(CC) $(CFLAGS) -S -o $@ $< + +-include .*.d + + + diff --git a/unix/syslinux.c b/unix/syslinux.c new file mode 100644 index 0000000..182e1bf --- /dev/null +++ b/unix/syslinux.c @@ -0,0 +1,487 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 1998-2004 H. Peter Anvin - All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * syslinux.c - Linux installer program for SYSLINUX + * + * This program ought to be portable. I hope so, at least. + * + * This is an alternate version of the installer which doesn't require + * mtools, but requires root privilege. + */ + +#ifdef __KLIBC__ +# define DO_DIRECT_MOUNT 1 /* Call mount(2) directly */ +#else +# define DO_DIRECT_MOUNT 0 /* Call /bin/mount instead */ +#endif + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 500 /* For pread() pwrite() */ +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslinux.h" +#include "libfat.h" + +#if DO_DIRECT_MOUNT + +# include + +#else + +# include +# ifndef _PATH_MOUNT +# define _PATH_MOUNT "/bin/mount" +# endif +# ifndef _PATH_UMOUNT +# define _PATH_UMOUNT "/bin/umount" +# endif + +#endif + +const char *program; /* Name of program */ +const char *device; /* Device to install to */ +pid_t mypid; +char *mntpath = NULL; /* Path on which to mount */ +#if DO_DIRECT_MOUNT +int loop_fd = -1; /* Loop device */ +#endif + +void __attribute__((noreturn)) usage(void) +{ + fprintf(stderr, "Usage: %s [-sf] [-o offset] device\n", program); + exit(1); +} + +void __attribute__((noreturn)) die(const char *msg) +{ + fprintf(stderr, "%s: %s\n", program, msg); + +#if DO_DIRECT_MOUNT + if ( loop_fd != -1 ) { + ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */ + close(loop_fd); + loop_fd = -1; + } +#endif + + if ( mntpath ) + unlink(mntpath); + + exit(1); +} + +/* + * read/write wrapper functions + */ +ssize_t xpread(int fd, void *buf, size_t count, off_t offset) +{ + ssize_t rv; + ssize_t done = 0; + + while ( count ) { + rv = pread(fd, buf, count, offset); + if ( rv == 0 ) { + die("short read"); + } else if ( rv == -1 ) { + if ( errno == EINTR ) { + continue; + } else { + perror(program); + exit(1); + } + } else { + offset += rv; + done += rv; + count -= rv; + } + } + + return done; +} + +ssize_t xpwrite(int fd, void *buf, size_t count, off_t offset) +{ + ssize_t rv; + ssize_t done = 0; + + while ( count ) { + rv = pwrite(fd, buf, count, offset); + if ( rv == 0 ) { + die("short write"); + } else if ( rv == -1 ) { + if ( errno == EINTR ) { + continue; + } else { + perror(program); + exit(1); + } + } else { + offset += rv; + done += rv; + count -= rv; + } + } + + return done; +} + +/* + * Version of the read function suitable for libfat + */ +int libfat_xpread(void *pp, void *buf, size_t secsize, libfat_sector_t sector) +{ + off_t offset = (off_t)sector * secsize; + return xpread((int)pp, buf, secsize, offset); +} + +int main(int argc, char *argv[]) +{ + static unsigned char sectbuf[512]; + unsigned char *dp; + const unsigned char *cdp; + int dev_fd, fd; + struct stat st; + int nb, left; + int err = 0; + pid_t f, w; + int status; + char mntname[64], devfdname[64]; + char *ldlinux_name, **argp, *opt; + int force = 0; /* -f (force) option */ + off_t offset = 0; /* -o (offset) option */ + struct libfat_filesystem *fs; + libfat_sector_t s, *secp, sectors[65]; /* 65 is maximum possible */ + int32_t ldlinux_cluster; + int nsectors; + + (void)argc; /* Unused */ + + program = argv[0]; + mypid = getpid(); + + device = NULL; + + umask(077); + + for ( argp = argv+1 ; *argp ; argp++ ) { + if ( **argp == '-' ) { + opt = *argp + 1; + if ( !*opt ) + usage(); + + while ( *opt ) { + if ( *opt == 's' ) { + syslinux_make_stupid(); /* Use "safe, slow and stupid" code */ + } else if ( *opt == 'f' ) { + force = 1; /* Force install */ + } else if ( *opt == 'o' && argp[1] ) { + offset = (off_t)strtoull(*++argp, NULL, 0); /* Byte offset */ + } else { + usage(); + } + opt++; + } + } else { + if ( device ) + usage(); + device = *argp; + } + } + + if ( !device ) + usage(); + + /* + * First make sure we can open the device at all, and that we have + * read/write permission. + */ + dev_fd = open(device, O_RDWR); + if ( dev_fd < 0 || fstat(dev_fd, &st) < 0 ) { + perror(device); + exit(1); + } + + if ( !force && !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) ) { + die("not a block device or regular file (use -f to override)"); + } + + if ( !force && offset != 0 && !S_ISREG(st.st_mode) ) { + die("not a regular file and an offset specified (use -f to override)"); + } + + xpread(dev_fd, sectbuf, 512, offset); + fsync(dev_fd); + + /* + * Check to see that what we got was indeed an MS-DOS boot sector/superblock + */ + if(!syslinux_check_bootsect(sectbuf,device)) { + exit(1); + } + + /* + * Now mount the device. + */ + if ( geteuid() ) { + die("This program needs root privilege"); + } else { + int i = 0; + struct stat dst; + int rv; + + /* We're root or at least setuid. + Make a temp dir and pass all the gunky options to mount. */ + + if ( chdir("/tmp") ) { + perror(program); + exit(1); + } + +#define TMP_MODE (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IWOTH|S_IXOTH|S_ISVTX) + + if ( stat(".", &dst) || !S_ISDIR(dst.st_mode) || + (dst.st_mode & TMP_MODE) != TMP_MODE ) { + die("possibly unsafe /tmp permissions"); + } + + for ( i = 0 ; ; i++ ) { + snprintf(mntname, sizeof mntname, "syslinux.mnt.%lu.%d", + (unsigned long)mypid, i); + + if ( lstat(mntname, &dst) != -1 || errno != ENOENT ) + continue; + + rv = mkdir(mntname, 0000); + + if ( rv == -1 ) { + if ( errno == EEXIST || errno == EINTR ) + continue; + perror(program); + exit(1); + } + + if ( lstat(mntname, &dst) || dst.st_mode != (S_IFDIR|0000) || + dst.st_uid != 0 ) { + die("someone is trying to symlink race us!"); + } + break; /* OK, got something... */ + } + + mntpath = mntname; + +#if DO_DIRECT_MOUNT + if ( S_ISREG(st.st_mode) ) { + /* It's file, need to mount it loopback */ + unsigned int n = 0; + struct loop_info64 loopinfo; + + for ( n = 0 ; loop_fd < 0 ; n++ ) { + snprintf(devfdname, sizeof devfdname, "/dev/loop%u", n); + loop_fd = open(devfdname, O_RDWR); + if ( loop_fd < 0 && errno == ENOENT ) { + die("no available loopback device!"); + } + if ( ioctl(loop_fd, LOOP_SET_FD, (void *)dev_fd) ) { + close(loop_fd); loop_fd = -1; + if ( errno != EBUSY ) + die("cannot set up loopback device"); + else + continue; + } + + if ( ioctl(loop_fd, LOOP_GET_STATUS64, &loopinfo) || + (loopinfo.lo_offset = offset, + ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo)) ) + die("cannot set up loopback device"); + } + } else { + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + } + + if ( mount(devfdname, mntpath, "msdos", + MS_NOEXEC|MS_NOSUID, "umask=077,quiet") ) + die("could not mount filesystem"); + +#else + + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + + f = fork(); + if ( f < 0 ) { + perror(program); + rmdir(mntpath); + exit(1); + } else if ( f == 0 ) { + char mnt_opts[128]; + if ( S_ISREG(st.st_mode) ) { + snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,loop,offset=%llu,umask=077,quiet", + (unsigned long long)offset); + } else { + snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,umask=077,quiet"); + } + execl(_PATH_MOUNT, _PATH_MOUNT, "-t", "msdos", "-o", mnt_opts,\ + devfdname, mntpath, NULL); + _exit(255); /* execl failed */ + } + + w = waitpid(f, &status, 0); + if ( w != f || status ) { + rmdir(mntpath); + exit(1); /* Mount failed */ + } + +#endif + } + + ldlinux_name = alloca(strlen(mntpath)+13); + if ( !ldlinux_name ) { + perror(program); + err = 1; + goto umount; + } + sprintf(ldlinux_name, "%s/ldlinux.sys", mntpath); + + unlink(ldlinux_name); + fd = open(ldlinux_name, O_WRONLY|O_CREAT|O_TRUNC, 0444); + if ( fd < 0 ) { + perror(device); + err = 1; + goto umount; + } + + cdp = syslinux_ldlinux; + left = syslinux_ldlinux_len; + while ( left ) { + nb = write(fd, cdp, left); + if ( nb == -1 && errno == EINTR ) + continue; + else if ( nb <= 0 ) { + perror(device); + err = 1; + goto umount; + } + + dp += nb; + left -= nb; + } + + /* + * I don't understand why I need this. Does the DOS filesystems + * not honour the mode passed to open()? + */ + fchmod(fd, 0400); + + close(fd); + + sync(); + +umount: +#if DO_DIRECT_MOUNT + + if ( umount2(mntpath, 0) ) + die("could not umount path"); + + if ( loop_fd != -1 ) { + ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */ + close(loop_fd); + loop_fd = -1; + } + +#else + + f = fork(); + if ( f < 0 ) { + perror("fork"); + exit(1); + } else if ( f == 0 ) { + execl(_PATH_UMOUNT, _PATH_UMOUNT, mntpath, NULL); + } + + w = waitpid(f, &status, 0); + if ( w != f || status ) { + exit(1); + } + +#endif + + sync(); + rmdir(mntpath); + + if ( err ) + exit(err); + + /* + * Now, use libfat to create a block map. This probably + * should be changed to use ioctl(...,FIBMAP,...) since + * this is supposed to be a simple, privileged version + * of the installer. + */ + fs = libfat_open(libfat_xpread, (void *)dev_fd); + ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL); + secp = sectors; + nsectors = 0; + s = libfat_clustertosector(fs, ldlinux_cluster); + while ( s && nsectors < 65 ) { + *secp++ = s; + nsectors++; + s = libfat_nextsector(fs, s); + } + libfat_close(fs); + + /* + * Patch ldlinux.sys and the boot sector + */ + syslinux_patch(sectors, nsectors); + + /* + * Write the now-patched first sector of ldlinux.sys + */ + xpwrite(dev_fd, syslinux_ldlinux, 512, offset + ((off_t)sectors[0] << 9)); + + /* + * To finish up, write the boot sector + */ + + /* Read the superblock again since it might have changed while mounted */ + xpread(dev_fd, sectbuf, 512, offset); + + /* Copy the syslinux code into the boot sector */ + syslinux_make_bootsect(sectbuf); + + /* Write new boot sector */ + xpwrite(dev_fd, sectbuf, 512, offset); + + close(dev_fd); + sync(); + + /* Done! */ + + return 0; +} + diff --git a/version b/version index ae656d4..a4b5a6f 100644 --- a/version +++ b/version @@ -1 +1 @@ -2.13 +2.20 -- 2.7.4