\
\ CDDL HEADER START
\
\ The contents of this file are subject to the terms of the
\ Common Development and Distribution License (the "License").
\ You may not use this file except in compliance with the License.
\
\ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
\ or http://www.opensolaris.org/os/licensing.
\ See the License for the specific language governing permissions
\ and limitations under the License.
\
\ When distributing Covered Code, include this CDDL HEADER in each
\ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
\ If applicable, add the following below this CDDL HEADER, with the
\ fields enclosed by brackets "[]" replaced with your own identifying
\ information: Portions Copyright [yyyy] [name of copyright owner]
\
\ CDDL HEADER END
\
\
\
\ Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
\ Use is subject to license terms.
\

purpose: HSFS file system support package for NewBoot
copyright: Copyright 2009 Sun Microsystems, Inc. All Rights Reserved

\ High Sierra, Rock Ridge (CD-ROM) file system reader and boot block

headers
" /packages" get-package  push-package

new-device
   fs-pkg$  device-name  diag-cr?

   \
   \	HSFS variables
   \
   0 instance value dev-ih
   0 instance value vol-desc
   0 instance value dir-buf
   0 instance value sua-buf
   0 instance value ce-buf

   \
   \	HSFS volume descriptor routines
   \

   \ unaligned load of 2-byte item
   : xw@  ( adr -- n )
      dup c@ swap char+   ( c0 adr+1 )
      c@                  ( c0 c1 )
      bwjoin
   ;

   \ unaligned store of 2-byte item
   : xw!  ( n adr -- )
      swap wbsplit swap 2 pick c! swap char+ c!
   ;

   \ unaligned load of 4-byte item
   : xl@  ( adr -- n )
      dup xw@ swap wa1+   ( w0 adr+2 )
      xw@                 ( w0 w1 )
      wljoin
   ;
   \ unaligned store of 4-byte item
   : xl!  ( n adr -- )
      swap lwsplit swap 2 pick xw! swap wa1+ xw!
   ;

   d# 2048 constant /sector
   d# 16 constant vol-desc-sector#  ( -- n )

   : +vd  ( index -- adr )
      vol-desc 0= if
         ." invalid access of +vd" cr abort
      then
      vol-desc +
   ;

   : root-dir  ( -- n )  d# 156 +vd  ;
   : /block    ( -- n )  d# 128 +vd xw@  ;
   : byte>blkoff  ( byte-off -- block-off )  /block mod  ;

   : get-vol-desc  ( -- )
      vol-desc  /sector  vol-desc-sector# /sector *  dev-ih  read-disk
   ;

   : read-fs-blocks  ( adr len fs-blk# -- )  /block *  dev-ih  read-disk  ;

   \
   \	HSFS directory routines
   \

   \ Current directory variables.
   instance variable cdir-blk		\ Current directory device block ptr.
   instance variable cdir-blk0          \ Current directory block0.
   instance variable cdir-offset	\ Current directory logical offset.
   instance variable cdir-size		\ Current directory logical size.
   instance variable cdir-ptr		\ Current directory entry pointer.
   false instance value cdir-rescan     \ Rescan current directory for symlink.

   \ Access of current directory entry.
   : +dr  ( n -- adr )  cdir-ptr @ +  ;

   : dir-entrylen    ( -- n )    d# 0  +dr c@  ;
   : dir-block0      ( -- n )    d# 2  +dr xl@  ;
   : dir-filesize    ( -- n )    d# 10 +dr xl@  ;
   : dir-flags       ( -- n )    d# 25 +dr c@  ;
   : dir-filenamelen ( -- n )    d# 32 +dr c@  ;
   : dir-filename    ( -- adr )  d# 33 +dr  ;

   : dir-isdir?      ( -- flag )  dir-flags  h# 02  and  0<>  ;
   : dir-file$       ( -- adr len )  dir-filename  dir-filenamelen  ;
   : dir-sualen      ( -- len )  dir-entrylen  d# 33 -  dir-filenamelen -  ;

   \ ISO name, including dot & dot-dot check
   : dir-iso$        ( -- adr len )
      dir-filenamelen 1  =  if
         dir-filename c@             ( name[0] )
         dup 0=  if
            drop " ."  exit          ( dot )
         then
         1 =  if                     (  )
            " .."  exit              ( dot-dot )
         then
      then
      dir-file$                      ( name$ )
   ;

   false instance value symlink?

   : get-dirblk  ( -- )
      dir-buf /block  cdir-blk @  read-fs-blocks
      1 cdir-blk +!
   ;

   : froot  ( -- )  root-dir cdir-ptr !  ;

   \
   \ SUAs - System Use Area in directory entry (Rock Ridge
   \  Extensions to High Sierra/ISO 9660 Format).
   \  Immediately follows directory entry name rounded up to
   \  a half-word boundary.
   \
   0 instance value sua-ptr
   0 instance value sua-len

   : +suf           ( n -- adr )    sua-ptr +  ;
   : suf-sig        ( -- adr len )  sua-ptr 2  ;
   : suf-len        ( -- len )      2 +suf c@  ;
   : suf-dat        ( -- data )     5 +suf  ;
   : suf-ce-lbn     ( -- lbn )      4 +suf xl@ ;
   : suf-ce-offset  ( -- offset )   d# 12 +suf xl@ ;
   : suf-ce-len     ( -- len )      d# 20 +suf xl@ ;

   : init-sua     ( -- )
      dir-file$ +  /w roundup  to sua-ptr
      dir-sualen               to sua-len
   ;

   : next-suf  ( -- )
      sua-len suf-len -  to sua-len
      suf-len +suf       to sua-ptr
   ;

   : end-sua  ( -- end? )
      sua-len 4 <
   ;

   : suf-nm$  ( -- adr len )  suf-dat  suf-len 5 -  ;

   \ Continuation suffix handling.  When a 'CE' suffix is seen,
   \ record the CE parameters (logical block#, offset and length
   \ of continuation).  We process the CE continuation only after
   \ we've finished processing the current SUA area.
   instance variable ce-lbn
   instance variable ce-offset
   instance variable ce-len
   : suf-ce-set  ( -- )
      suf-ce-lbn ce-lbn !
      suf-ce-offset ce-offset !
      suf-ce-len ce-len !
   ;
      
   : suf-ce-process  ( -- error? )
      ce-lbn @ 0= if
         true
      else
         sua-buf ce-len @ ce-lbn @  read-fs-blocks
         sua-buf   to sua-ptr
         ce-len @  to sua-len
         0 ce-len ! 0 ce-lbn ! 0 ce-offset !
         false
      then
   ;

   /buf-len  instance buffer: suf-sl-buf
   false     instance value   symlink-need-sep

   \ Format of Rock Ridge symlinks needs to be munged to unix-style
   \ name.  Format is:  <flag><nbytes>file-name<flag><nbytes>filename...
   \ where \ <flag> is flag byte (0=filename, 2=current dir, 4=parent
   \ dir, 8=root dir) and <nbytes> is one-byte byte count (zero for
   \ !filename).
   : suf-copy-to-symlinkbuf  ( name$  -- )
       false to symlink-need-sep
       suf-sl-buf -rot bounds do           ( dst )
          symlink-need-sep if
             ascii / over c! char+
          then
          true to symlink-need-sep
          i c@ dup 2 = if                   ( dst 2 )
             \ CURRENT (".")
             drop ascii . over c! char+ 2   ( dst' inc )
          else  dup 4 =  if                 ( dst 4 )
             \ PARENT ("..")
             drop " .." 2 pick swap move    ( dst )
             wa1+ 2                         ( dst' inc )
          else  dup 8 =  if                 ( dst 8 )
             \ ROOT ("/")
             drop ascii / over c! char+ 2   ( dst' inc )
             false to symlink-need-sep
          else  dup 0<> if
             ." unknown SL flag: " .x cr abort
          else                              ( dst c )
             drop                           ( dst )
             i char+ dup c@ >r              ( dst src+1  R:nbytes )
             char+ over r@ move             ( dst R:nbytes )
             r@ +                           ( dst' R:nbytes )
             r> wa1+                        ( dst' inc )
          then then then then
       +loop                                ( dst )
       0 swap c!
    ; 

   \ Saved 'NM' prefix buffer.
   /buf-len  instance buffer: suf-nm-buf
   0 instance value suf-nm-size
      
   \ Return the Rock Ridge file name associated with the current
   \ dirent ('NM' suffix).  Otherwise returns standard iso filename.
   \ Marks whether returned filename is a symbolic link ('SL' suffix)
   \ and also processes continuations ('CE' suffix).
   : rr-file$ ( -- adr len )
      false to symlink?
      0 to suf-nm-size

      \ select start of sua, record sua offset
      init-sua
      begin
         end-sua  if
            suf-ce-process if
               suf-nm-size if
                  suf-nm-buf suf-nm-size       ( NM$ )
               else
                  dir-iso$                     ( iso$ )
               then                            ( file$ )
               exit
            then
         then
         suf-sig                               ( sig-adr sig-len )
         2dup " NM"  $=  if
            suf-nm$ to suf-nm-size             ( sig-adr sig-len suf-nm-adr )
            suf-nm-buf suf-nm-size move
         then                                  ( sig-adr sig-len )
         2dup " SL"  $=  if
            true to symlink?
            suf-nm$ suf-copy-to-symlinkbuf
         then
         2dup " CE"  $=  if
            suf-ce-set
         then                                  ( sig-adr sig-len )
         2drop  next-suf                       (  )
      again
   ;

   \
   \	HSFS high-level routines
   \

   \ Used for rescanning current directory for symbolic links.

   \ Initializes current directory settings from current directory
   \ entry pointer or for rescan.  If it's not a rescan, we have
   \ access to the actual directory entry, so we can check whether
   \ it's a directory or not here.
   : init-dent  ( -- error? )
      cdir-rescan if
         false to cdir-rescan
         cdir-blk0 @ cdir-blk !
      else
         dir-isdir? 0= if
            true exit
         then
         dir-block0 dup cdir-blk ! cdir-blk0 !
         dir-filesize cdir-size !
      then                                    ( blk0 size )
      0 cdir-offset !
      false
   ;

   : get-dent ( -- error? )
      begin
         \ Check for end of directory, return true if we're past the EOF.
         cdir-offset @  cdir-size @  >=  if
            true  exit
         then

         \ If we're at a block boundary, get the next block.  Otherwise
         \ increment the directory pointer.
         cdir-offset @ byte>blkoff  0=  if
            get-dirblk
            dir-buf cdir-ptr !
         else
            dir-entrylen cdir-ptr +!
         then

         \ If dir-entrylen is not zero, increment the current directory
         \ file offset.  Otherwise, a dir-entrylen of zero indicates
         \ the end of a dir block, so round up cdir-offset to fetch the
         \ next one
         dir-entrylen ?dup if
            cdir-offset +!  true
         else
            cdir-offset @  /block  roundup  cdir-offset !
            false
         then
      until  false
   ;

   \ Look through current directory for file name 'file$'.
   \ Will leave current directory entry (cdir-ptr) pointing
   \ to matched entry on success.
   : dirlook  ( file$ -- error? )
      init-dent if
         true exit
      then
      begin  get-dent 0=  while      ( file$ )
         2dup rr-file$ $=  if        ( file$ )
            2drop false  exit        ( succeeded )
         then                        ( file$ )
      repeat 2drop true              ( failed )
   ;

   /buf-len  instance buffer: symlink-buf
   : symlink-buf$  ( -- path$ )  symlink-buf cscount  ;

   : follow-symlink  ( tail$ -- tail$' )

      \ copy symlink value (plus null) to buf
      suf-sl-buf cscount 1+  symlink-buf swap  move
      false to symlink?

      \ append to current path
      ?dup  if                                              ( tail$ )
	 " /" symlink-buf$  $append                         ( tail$ )
	 symlink-buf$  $append                              (  )
      else  drop  then                                      (  )
      symlink-buf$                                          ( path$ )
      over c@  ascii /  =  if                               ( path$ )
	 froot  str++                                       ( path$' )
      else
         true to cdir-rescan
      then                                                  ( path$ )
   ;

   : lookup  ( path$ -- error? )
      over c@  ascii /  =  if
	 froot  str++                            ( path$' )
      then                                       ( path$ )
      begin                                      ( path$ )
         ascii / left-parse-string               ( path$ file$ )
      dup  while                                 ( path$ file$ )
         dirlook  if
            2drop true  exit                     ( failed )
         then                                    ( path$ )
         symlink?  if
            follow-symlink                       ( path$' )
         then                                    ( path$ )
      repeat                                     ( path$ file$ )
      2drop 2drop  false                         ( succeeded )
   ;


   \
   \	HSFS installation routines
   \

   \ Allocate memory for necessary data structures.  Need to
   \ read volume desriptor sector in order to get /block value.
   : initialize  ( -- error? )
      /sector  mem-alloc to vol-desc
      get-vol-desc
      /block   mem-alloc to dir-buf
      /block   mem-alloc to sua-buf
      /block   mem-alloc to ce-buf
   ;

   : release-buffers  ( -- )
      ce-buf      /block  mem-free
      sua-buf	  /block  mem-free
      dir-buf     /block  mem-free
      vol-desc    /sector mem-free
      0 to vol-desc
   ;


   \ HSFS file interface
   struct
      /x     field >filesize
      /x     field >offset
      /x     field >block0
   constant /file-record

   d# 10                  constant #opens
   #opens /file-record *  constant /file-records

   /file-records  instance buffer: file-records

   -1 instance value current-fd

   : fd>record  ( fd -- record )  /file-record *  file-records +  ;

   : set-fd  ( fd -- error? )
      dup 0 #opens 1 - between 0= if
         drop true exit
      then
      dup fd>record  >block0 x@ 0= if
         drop true exit
      then
      to current-fd false
   ;

   : file-offset@  ( -- off )
      current-fd fd>record >offset x@
   ;

   : file-offset!  ( off -- )
      current-fd fd>record >offset x!
   ;

   : file-size@  ( -- size )
      current-fd fd>record >filesize x@
   ;

   : file-size!  ( size -- )
      current-fd fd>record >filesize x!
   ;

   : file-block0@  ( -- block0 )
      current-fd fd>record >block0 x@
   ;

   : file-block0!  ( block0 -- )
      current-fd fd>record >block0 x!
   ;

   : get-slot  ( -- fd false | true )
      #opens 0  do
         i fd>record >block0 x@  0=  if
            i false  unloop exit
         then
      loop  true
   ;

   : free-slot  ( fd -- )
      set-fd 0= if
         0 file-offset!
         0 file-size!
         0 file-block0!
      then
   ;

   \ initializes the open structure with information from
   \ the inode (on UFS) or directory entry (from HSFS).
   : init-fd  ( fd -- )
      to current-fd
      dir-block0 file-block0!
      dir-filesize file-size!
      0 file-offset!
   ;

   external

   : open ( -- okay? )
      my-args dev-open  dup 0=  if       ( 0 )
         exit                            ( failed )
      then  to dev-ih

      initialize  froot
      file-records /file-records  erase
      true                               ( succeeded )
   ;

   : close  ( -- )
      dev-ih dev-close
      release-buffers
   ;

   : open-file  ( path$ -- fd true | false )
      get-slot  if
	 2drop false  exit            ( failed )
      then  -rot                      ( fd path$ )

      lookup  if                      ( fd )
	 drop false  exit             ( failed )
      then

      dup init-fd true                ( fd success )
   ;

   : close-file  ( fd -- )
      free-slot   (  )
   ;

   : read-file   ( adr len fd -- #read )

      \ Check if fd is valid, if it is set current-fd.
      set-fd if
         2drop 0 exit
      then                                   ( adr len )

      \ Adjust len if less than len bytes remain.
      file-size@ file-offset@ - min          ( adr len' )

      \ Check for invalid length read.
      dup 0<=  if  2drop 0 exit  then

      \ Compute physical device byte offset.
      tuck                                   ( len adr len )
      file-block0@ /block * file-offset@ +   ( len adr len off )
      dev-ih read-disk                       ( #read )
      dup file-offset@ +  file-offset!
   ;

   : seek-file  ( off fd -- error? )
      set-fd  if                ( off )
         drop false  exit       ( failed )
      then                      ( off )

      dup file-size@ >  if      ( off )
         drop false  exit       ( failed )
      then                      ( off )
      dup  file-offset!  true   ( off succeeded )
   ;

   : size-file  ( fd -- size )
      set-fd if
         0
      else
         file-size@
      then
   ;

   \ we don't support compression (yet)
   : cinfo-file  ( fd -- bsize fsize comp? )
      set-fd  if  0 0 0  else  /block file-size@ 0  then
   ;

   \ read ramdisk fcode at rd-offset
   : get-rd   ( adr len -- )
      rd-offset dev-ih  read-disk
   ;

   \ no additional props needed for hsfs
   : bootprop  ( -- )  false  ;

   \ debug words
   : chdir  ( path$ -- )
      2dup lookup if
         type ."  Not found" cr
      else
         dir-isdir? 0= if
            type ."  Not a directory" cr
         else
            type
	    ."  blk0 "
            cdir-blk0 @ .x
            ."  size "
            cdir-size @ .x
            cr
         then
      then
   ;

   : dir  ( -- )
      init-dent
      begin  get-dent 0=  while
         rr-file$ type
         ."  flags " dir-flags .x
         ." blk0 " dir-block0 .x
         ." size " dir-filesize .x
         cr
      repeat
      true to cdir-rescan
   ;
      

finish-device
pop-package