blob: 8da4c15d47491eebff7b001a7046f27e8b080a86 [file] [log] [blame]
purpose: Macintosh File System support package
\ See license at end of file
dev /packages
new-device
" hfs-file-system" device-name
0 0 " support" property
headerless
0 instance value /al-blk
0 instance value al-blk0
\ The "extents" structure describes a contiguous piece of a file.
\ They are stored in groups of 3. The first group of three is
\ stored in:
\ a) The catalog file node, for ordinary file data and resource forks
\ b) The volume info record, for the catalog file and the extents
\ overflow file.
\ Subsequent groups of three are stored in node records within the
\ extents overflow file, tagged by file ID and which fork. The
\ extents overflow file itself is described solely by its first
\ three extent descriptors, and does not use overflow extents to
\ describe itself.
struct ( extent )
/w field >first-al-blk
/w field >#al-blks
constant /extent
/extent 3 * constant /extents
\ Given a target logical block number, the address of an array of 3 extents
\ entries, and the number of the logical block represented by the first
\ entry in that array, find the physical block number corresponding to the
\ target logical block number and the number of blocks after it in the
\ enclosing extent.
: (next-piece) ( log-blk# extents-adr extent-log-blk -- phys-blk# #blks )
3 0 do ( log-blk# extent extent-blk# )
over >#al-blks be-w@ + ( log-blk# extent extent-max-blk# )
2 pick over u< if leave then ( log-blk# extent extent-max-blk# )
swap /extent + swap ( log-blk# extent' extent-max-blk# )
loop ( log-blk# extent' extent-max-blk# )
swap >r swap - ( #blks )
r@ >first-al-blk be-w@ r> >#al-blks be-w@ + over - swap
;
\ Determine the total number of blocks described by the array of 3 extents
\ entries beginning at extents-adr
: extent-#blks ( extents-adr -- #blks )
0 swap >#al-blks /extents bounds do i be-w@ + /extent +loop
;
\ A node record is is a fixed length container that contains one or more
\ variable-length nodes.
struct ( node-record )
/l field >flink
/l field >blink
/c field >type
/c field >level
/w field >#recs
h# 1f4 +
constant /node-record
/node-record instance buffer: node-record
\ Each B*Tree file begins with a header node.
struct ( header-node )
/w field >tree-depth
/l field >root-index
/l field >#leaf-records
/l field >first-leaf
/l field >last-leaf
/w field >reclen
/w field >whatever1
/l field >total#nodes
/l field >#free-nodes
constant /master-index
\ Nodes in the catalog file come in three flavors - thread, directory, and file
struct ( thread-catalog-entry ) \ Type = 3 or 4
1 +
d# 8 +
/l field >thread-parent
d# 32 field >thread-name
drop
struct ( times )
/l field >create-time
/l field >modify-time
/l field >backup-time
constant /times
struct ( dir-catalog-entry )
1 +
/w field >dir-flags
/w field >valence
/l field >dir-id
/times field >dir-times
d# 16 field >dir-user-finder-info
d# 16 field >dir-finder-info
d# 16 field >dir-reserved
drop
struct ( file-catalog-entry )
1 +
/c field >file-flags \ h#80:record used 2:thread rec exists 1:locked
/c field >file-type \ Supposed to always contain 0
d# 16 field >user-finder
/l field >file#
/w field >dblk0
/l field >dleof
/l field >dpeof
/w field >rblk0
/l field >rleof
/l field >rpeof
/times field >file-times
d# 16 field >finder-info
/w field >/clump
/extents field >data-extents
/extents field >resource-extents
/l + ( reserved )
constant /catalog-entry
: skip-key ( adr -- adr' ) count 1 or + ;
\ Each HFS volume is described by a volume-info structure that is stored
\ at offset 1K bytes from the beginning of the volume.
struct ( volume-info )
/w field >magic
/l field >init-date
/l field >mode-date
/w field >volume-attributes
/w field >volume-#files
/w field >bitmap-blk#
/w field >al-ptr
/w field >vol-#al-blks
/l field >/al-blk
/l field >vol-/clump
/w field >al-blk0
/l field >next-file#
/w field >#free-blocks
h# 1c field >vol-name
/l field >bkup-date
/w field >vseq#
/l field >write-count
/l field >/extent-clump
/l field >/catalog-clump
/w field >#rt-dirs
/l field >#files
/l field >#directories
d# 32 field >vol-finder-info
/w field >vc-size
/w field >vcbm-size
/w field >ctlc-size
/l field >/extent-tree
/extents field >extent-extents
/l field >/catalog-tree
/extents field >catalog-extents
h# 15e + \ There's something at offset 0x5f4 too
constant /volume-info
/volume-info instance buffer: volume-info
3 constant extent-file-id
4 constant catalog-file-id
\ This forward reference is necessary because B*Trees are stored in
\ files that are described by extents, some of which can be stored in
\ the extents overflow B*Tree file. Infinite recursion is prevented by
\ ensuring that the extents overflow file is contained entirely within
\ the three fixed extents, so that file does not need overflow extents.
defer search-btree
: >extent-file-blk# ( log-blk# -- phys-blk# )
volume-info >extent-extents 0 (next-piece) drop
;
: extent-compare ( key-adr1 key-adr2 -- -|0|+ )
over ca1+ c@ over ca1+ c@ - ?dup if ( key-adr1 key-adr2 +- )
over wa1+ be-l@ over wa1+ be-l@ - ?dup if ( key-adr1 key-adr2 +- )
swap 6 + be-w@ swap 6 + be-w@ - ( +|0|- )
else ( key-adr1 key-adr2 )
nip nip ( +|- )
then ( +|0|- )
else ( key-adr1 key-adr2 )
nip nip ( +|- )
then ( +|0|- )
;
: find-extent ( al-blk# file-id fork -- node-adr )
d# 10 alloc-mem >r ( al-blk# file-id fork r: key-buffer )
r@ ca1+ c! ( al-blk# file-id )
r@ wa1+ be-l! ( al-blk# )
r@ 6 + be-w! ( )
r@ ['] extent-compare extent-file-id search-btree
r> d# 10 free-mem
0= abort" Can't find extent"
;
: >catalog-file-blk# ( log-blk# -- phys-blk# )
0 volume-info >catalog-extents >r ( log-blk# 0 r: entents-adr )
begin 2dup r@ extent-#blks + u> while ( log-blk# blk# r: extents-adr )
r> extent-#blks + ( log-blk# blk#' )
dup catalog-file-id 0 find-extent ( log-blk# blk# node-adr )
skip-key >r ( log-blk# blk# r: extents-adr' )
repeat ( log-blk# blk# r: extents-adr )
\ Now log-blk# is within the group of extents at "extents-adr" and
\ blk# is the beginning logical block of that extents group.
r> swap (next-piece) drop ( phys-blk# )
;
: al-blk>offset ( al-blk# -- d.disk-offset ) /al-blk um* al-blk0 0 d+ ;
0 instance value btree-file#
\ Return the physical disk offset of the indicated node record from the file
\ "btree-file#",
: >node-rec-offset ( rec# -- d.disk-offset )
/node-record um* /al-blk um/mod 0 swap ( d.blk-offset log-blk# )
btree-file# case
extent-file-id of >extent-file-blk# endof
catalog-file-id of >catalog-file-blk# endof
endcase ( d.blk-offset phys-blk# )
al-blk>offset d+ ( d.disk-offset )
;
\ "Cache tags" to remember which node is currently in the buffer
-1 instance value node-rec#
-1 instance value node-file#
: >node-rec ( rec# -- record-adr )
dup node-rec# = btree-file# node-file# = and if
drop node-record exit
then ( rec# )
dup >node-rec-offset " seek" $call-parent drop ( rec# )
node-record /node-record " read" $call-parent drop ( rec# )
\ Update "cache tags"
to node-rec# btree-file# to node-file# ( )
node-record
;
: >nextnode ( node# rec# -- node#' rec#' )
dup >node-rec dup >#recs be-w@ ( node# rec# rec& #recs )
3 pick 1+ ( node# rec# rec& #recs node#' )
<= if ( node# rec# rec& )
nip nip >flink be-l@ 0 swap ( 0 rec#' )
else ( node# rec# rec& )
drop swap 1+ swap ( node#' rec# )
then
;
: >node ( node# rec# -- node-adr )
>node-rec dup /node-record + rot 1+ /w* - be-w@ +
;
defer key-compare
: search-ply ( key-adr node# rec# -- key-adr node#' rec#' )
2>r 2r@
begin
3dup >node key-compare ( key-adr node#' rec#' )
?dup 0= if ( key-adr node#' rec#' )
2r> 2drop exit
then ( key-adr node#' rec#' flag )
0< if ( key-adr node#' rec#' )
2drop 2r> exit
then ( key-adr node#' rec#' )
2r> 2drop 2>r ( key-adr node#' rec#' )
2r@ >nextnode 2dup or while ( key-adr node#' rec#' )
repeat ( key-adr node#' rec#' )
2drop 2r>
;
0 instance value last-node#
0 instance value last-rec#
: (search-btree) ( key-adr compare-xt btree-file# -- false | node-adr true )
btree-file# >r to btree-file#
['] key-compare behavior >r to key-compare ( key-adr r: old-file# old-xt )
0 0 >node >root-index be-l@ 0 swap ( key-adr node# rec# )
begin ( key-adr node# rec# )
search-ply ( key-adr node#' rec#' )
dup >node-rec >level c@ 1 > ( key-adr node#' rec#' leval )
while ( key-adr node# rec# )
\ Go down a level
>node skip-key be-l@ 0 swap ( key-adr node#' rec#' )
repeat ( key-adr node# rec# )
\ If we are searching the catalog tree, save the node and record
\ numbers for the benefit of next-file
btree-file# catalog-file-id = if
2dup to last-rec# to last-node#
then
>node tuck key-compare if ( node-adr )
drop false
else
true
then
r> to key-compare r> to btree-file#
;
' (search-btree) to search-btree
: read-blocks ( adr blk# #blks -- )
swap al-blk>offset " seek" $call-parent drop ( adr #blks )
/al-blk * " read" $call-parent drop
;
: get-catalog ( -- )
h# 400 0 " seek" $call-parent drop
volume-info /volume-info " read" $call-parent drop
volume-info >r
r@ >magic be-w@ h# 4244 <> abort" Bad volume magic number"
r@ >/al-blk be-l@ to /al-blk
r@ >al-blk0 be-w@ h# 200 * to al-blk0
r> drop
;
: name-compare ( name-adr1 name-adr2 -- -|0|+ )
>r count tuck ( len1 adr1 len1 r: name-adr2 )
r> count dup >r ( len1 adr1 len1 adr2 len2 r: len2 )
rot min caps-comp ?dup if ( len1 +|- r: len2 )
nip r> drop ( +|- )
else ( len1 r: len2 )
r> - ( +|- )
then
;
: path-compare ( key-adr1 key-adr2 -- -|0|+ )
dup c@ 0= if 2drop 1 exit then ( key-adr1 key-adr2 )
swap wa1+ swap wa1+ ( dirid-adr1 dirid-adr2 )
over be-l@ over be-l@ - ?dup if ( dirid-adr1 dirid-adr2 -|+ )
nip nip ( -|+ )
else ( dirid-adr1 dirid-adr2 )
swap la1+ swap la1+ name-compare ( -|0|+ )
then
;
: type-compare ( key-adr1 key-adr2 -- -|0|+ )
dup c@ 0= if 2drop 1 exit then ( key-adr1 key-adr2 )
swap wa1+ swap wa1+ ( dirid-adr1 dirid-adr2 )
over be-l@ over be-l@ - ?dup if ( dirid-adr1 dirid-adr2 -|+ )
nip nip exit ( -|+ )
then
node-record >level c@ 1 = if ( dirid-adr1 dirid-adr2 )
swap la1+ swap la1+ ( adr1 adr2 )
skip-key dup c@ 2 = if ( adr1 filerec )
1+ >user-finder be-l@ swap ( type adr1 )
ca1+ be-l@ = if
0 ( 0 ) \ found it
else
1 ( 1 ) \ not the type we want
then
else ( adr1 filerec )
2drop 1 ( 1 ) \ not a file
then
else ( dirid-adr1 dirid-adr2 )
2drop -1 ( -1 ) \ not level 1
then
;
\ search string for %xx and replace with hex char
: translate-name ( adr len -- adr len' )
push-hex
2dup ( adr len adr len )
begin dup while ( adr len adr len )
over c@ [char] % = if ( adr len adr len )
rot 2 - -rot ( adr len' adr len )
over 1+ 2 $number 0= if ( adr len adr len n )
2 pick c! 2 - swap 1+ swap ( adr len adr' len' )
over dup 2+ swap 4 pick move ( adr len adr' len' )
then
else
swap 1+ swap 1- ( adr len adr' len' )
then
repeat
2drop
pop-base
;
d# 38 instance buffer: file-key
: find-file ( adr len dir-id -- false | node-adr true )
file-key wa1+ be-l! ( adr len )
translate-name ( adr len )
over c@ [char] : = if \ search by type
1 /string ( adr len )
file-key 6 + place ( )
file-key ['] type-compare catalog-file-id search-btree
else \ search by name
file-key 6 + place ( )
file-key ['] path-compare catalog-file-id search-btree
then
;
0 instance value next-log-blk
/extents buffer: extents
0 instance value extent-log-blk
0 instance value blk-offset
0 instance value the-fork
0 instance value current-node \ The catalog node that the last lookup found
0 instance value cwd-id \ The current directory; a parent if file-id !0
0 instance value file-id \ The current fileid, or 0 if the last lookup
\ found a directory
: follow-thread ( adr -- node-adr false | true true )
dup >thread-name count
rot >thread-parent be-l@ find-file if
false
else
true true
then
;
\ This can fail if you try to select an alias whose referent has been deleted
: select-component ( node-adr -- error? )
begin
dup to current-node
skip-key count ( adr type )
case
1 of >dir-id be-l@ to cwd-id false true endof
2 of >file# be-l@ to file-id false true endof
3 of follow-thread endof
4 of follow-thread endof
( default ) ." Bogus Record Type: " dup . nip cr
endcase
until
;
: select-file ( adr len -- error? )
dup 0= if 2drop exit then
0 to file-id
over c@ [char] \ = if
1 /string 2 to cwd-id
over c@ [char] \ = if \ blessed folder indicator?
1 /string volume-info >vol-finder-info be-l@ to cwd-id
then
\ " " cwd-id find-file if select-component else 2drop true exit then
then ( adr len )
begin dup while ( rem$ )
\ Error if we have already found a file but there's more in the path
file-id if
" %RESOURCES" $= if
h# ff to the-fork false
else
true
then
exit
then ( rem$ )
[char] \ left-parse-string ( rem$' name$ )
\ Error if we can't find the path component
cwd-id find-file 0= if 2drop true exit then ( rem$ node-adr )
select-component if 2drop true exit then ( rem$ )
repeat ( null$ )
2drop false
;
: do-seek ( d -- ) /al-blk um/mod to next-log-blk to blk-offset ;
: prime-extents ( -- ) -1 to extent-log-blk 0 0 do-seek ;
: extent-max-blk ( -- last-blk#+1 )
extent-log-blk -1 = if 0 exit then
extents extent-#blks extent-log-blk +
;
: get-extents ( log-blk# -- )
dup extent-log-blk u< if
0 to extent-log-blk
current-node skip-key 1+
the-fork if >resource-extents else >data-extents then
extents /extents move ( log-blk# )
then
\ Now ext-log-blk <= log-blk#
begin dup extent-max-blk u>= while
extent-max-blk to extent-log-blk
extent-log-blk file-id the-fork find-extent
skip-key extents /extents move
repeat
drop
\ Now ext-log-blk <= log-blk# < extent-max-blk
;
\ Returns the address and length of the next contiguous part of the file
\ following the given logical block number
: next-piece ( log-blk# -- phys-blk# #blks )
dup get-extents ( log-blk# )
extents extent-log-blk (next-piece) ( phys-blk# #blks )
;
: hfsdfalign ( d.byte# 'fh -- d.aligned )
drop /al-blk um/mod nip /al-blk um*
;
: this-size ( -- n )
current-node skip-key 1+ the-fork if >rleof else >dleof then be-l@
;
: hfsdflen ( 'fhandle -- d.size ) drop this-size 0 ;
: hfsdfseek ( d.byte# 'fh -- ) drop do-seek ;
: #remaining ( -- n ) this-size next-log-blk /al-blk * - 0 max ;
: hfsfread ( addr count 'fh -- #read )
drop ( adr len )
#remaining min dup >r ( adr #to-read )
/al-blk 1- + /al-blk / ( adr #blks-to-read )
begin dup while ( adr #blks-rem )
>r ( adr )
dup next-log-blk next-piece ( adr adr phys-blk# #blks )
r@ min dup >r ( adr adr phys-blk# #to-read )
read-blocks r> r> ( adr #to-read #blks-rem )
over - -rot ( #blks-rem' adr #to-read )
/al-blk * + swap ( adr' #blks-rem' )
repeat ( adr 0 )
2drop
r>
;
: hfsfclose ( 'fh -- ) drop bfbase @ /al-blk free-mem ;
/fd instance buffer: my-fd
: hfssetupfd ( -- )
0 0 do-seek
file @ >r my-fd file !
/al-blk alloc-mem /al-blk initbuf
my-fd r/o
['] hfsdflen ['] hfsdfalign ['] hfsfclose ['] hfsdfseek
['] nullwrite ['] hfsfread
setupfd
r> file !
;
headers
: open ( -- )
get-catalog
1 to cwd-id
-1 to extent-log-blk
my-args select-file if false exit then
file-id if hfssetupfd then
true
;
: close ( -- )
file-id if
my-fd ['] fclose catch ?dup if .error drop then
then
;
: read ( adr len -- actual )
my-fd ['] fgets catch if 3drop 0 then
;
\ : write ( adr len -- actual )
\ tuck my-fd ['] fputs catch if 2drop 2drop -1 then
\ ;
: seek ( offset.low offset.high -- error? )
my-fd ['] dfseek catch if 2drop true else false then
;
: size ( -- d ) my-fd ['] dfsize catch if 2drop 0 0 then ;
: load ( adr -- size ) size drop read ;
1 value showtype?
: file-name ( -- adr len )
current-node 6 + count ( adr len )
showtype? if
current-node skip-key c@ 2 = if
current-node skip-key 1+ >user-finder be-l@ ( adr len type )
file-key be-l! file-key 4 + dup h# 20 swap c! 1+ ( adr len buf )
swap dup >r move file-key r> 5 + ( adr len )
then
then
;
headerless
\ XXX Is this correct for directory records?
: locked? ( flags -- flag ) h# 100 and 0<> ;
\ Convert HFS file attributes to the firmware encoding
\ see showdir.fth for a description of the firmware encoding
create type-map h# 4000 , h# 8000 , 0 , h# a000 ,
: >canonical-attrs ( hfs-flags -- canon-attrs )
>r
\ Access permissions
r@ locked? if o# 666 else o# 777 then \ rwxrwxrwx
\ XXX maybe the way to set the "archive" indication is to compare
\ the backup time with the modification time.
\ Mutually-exclusive file types
current-node skip-key c@ 1- type-map swap na+ @ or
r> drop
;
create alias-times 0 be-l, 0 be-l, 0 be-l,
decimal
\ The months are stored in this order so the leap day happens at the
\ end of a cycle.
create days/month
\ Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb
31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 29 c,
: >d/m ( day-in-year -- day month )
d# 12 0 do
days/month i ca+ c@ 2dup < if ( rem days-in-month )
drop 1+ i leave
then
-
loop
;
\ The Mac filesystem stores timestamps in seconds since midnight on 1/1/1904
: mac-time> ( seconds -- s m h d m y )
\ XXX we should account for accumulated leap seconds too
60 u/mod 60 u/mod 24 u/mod ( s m h days )
60 - \ shift to first cycle boundary (1Mar04)
[ 365 4 * 1+ ] literal /mod >r ( s m h day-in-cycle r: cycles )
365 u/mod ( s m h day-in-year year-in-cycle )
dup 4 = if 2drop 365 3 then ( s m h day-in-year year-in-cycle )
swap >d/m ( s m h year-in-cycle day month )
9 - dup 0> if rot 1+ else 12 + rot then ( s m h d m year-in-cycle )
r> 4 * + 1904 +
;
hex
: get-mtime ( adr -- s m h d m y ) >modify-time be-l@ mac-time> ;
headers
: file-info ( -- s m h d m y len attributes name$ )
current-node skip-key count swap >r case ( r: adr )
1 of
r@ >dir-times get-mtime r@ >valence be-w@
endof
2 of
r@ >file-times get-mtime r@ >dleof be-l@ r@ >rleof be-l@ +
endof
( default )
>r alias-times get-mtime 0 r>
endcase ( s m h d m y len r: adr )
r> >dir-flags be-w@ >canonical-attrs ( s m h d m y len attrs )
file-name ( s m h d m y len attrs name$ )
;
headerless
: next-file ( -- found? )
catalog-file-id to btree-file#
last-node# last-rec# >nextnode 2dup to last-rec# to last-node#
2dup or 0= if 2drop false exit then ( node# rec# )
>node dup to current-node ( node-adr )
wa1+ be-l@ cwd-id =
;
headers
: next-file-info ( id -- false | id' s m h d m y len attributes name$ true )
dup 0= if
null$ cwd-id find-file if drop else drop false exit then
then ( id )
next-file if ( id )
1+ file-info true
else
drop false
then
;
: free-bytes ( -- d.#bytes ) volume-info >#free-blocks be-w@ /al-blk m* ;
finish-device
device-end
\ LICENSE_BEGIN
\ Copyright (c) 1997 FirmWorks
\
\ Permission is hereby granted, free of charge, to any person obtaining
\ a copy of this software and associated documentation files (the
\ "Software"), to deal in the Software without restriction, including
\ without limitation the rights to use, copy, modify, merge, publish,
\ distribute, sublicense, and/or sell copies of the Software, and to
\ permit persons to whom the Software is furnished to do so, subject to
\ the following conditions:
\
\ The above copyright notice and this permission notice shall be
\ included in all copies or substantial portions of the Software.
\
\ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\
\ LICENSE_END