blob: 66b2f7aa00f16a400ed2affa4ece2d45a6df3ed7 [file] [log] [blame]
\ See license at end of file
purpose: Linux ext2fs file system directories
decimal
2 constant root-dir#
0. instance 2value d.dir-block#
0 instance value lblk#
instance variable diroff
instance variable totoff
\ Information that we need about the working file/directory
\ The working file changes at each level of a path search
0 instance value wd-inum \ Inumber of directory to search
0 instance value wf-inum \ Inumber of file or directory found
0 instance value wf-type \ Type - 4 for directory, d# 10 for symlink, etc
: find-dirblk ( -- )
lblk# >d.pblk# 0= abort" EXT2 - missing directory block" to d.dir-block#
;
: get-dirblk ( -- end? )
lblk# bsize um* dfile-size d< 0= if true exit then
find-dirblk
false
;
\ **** Return the address of the current directory entry
: dirent ( -- adr ) d.dir-block# d.block diroff @ + ;
\ Dirent fields:
\ 00.l inode
\ 04.w offset to next dirent
\ 06.b name length
\ 07.b flags?
\ 08.s name string
: >reclen ( name-length -- record-length ) 8 + 4 round-up ;
: dirent-inode@ ( -- n ) dirent int@ ;
: dirent-inode! ( n -- ) dirent int! update ;
: dirent-len@ ( -- n ) dirent la1+ short@ ;
: dirent-len! ( n -- ) dirent la1+ short! update ;
: dirent-nameadr ( -- adr ) dirent la1+ 2 wa+ ;
: dirent-namelen@ ( -- b ) dirent la1+ wa1+ c@ ;
: dirent-namelen! ( b -- ) dirent la1+ wa1+ c! update ;
: dirent-type@ ( -- b ) dirent la1+ wa1+ ca1+ c@ ;
: dirent-type! ( b -- ) dirent la1+ wa1+ ca1+ c! update ;
: dirent-reclen ( -- n ) dirent-namelen@ >reclen ;
: lblk#++ ( -- ) lblk# 1+ to lblk# ;
: dirent-vars ( -- diroff totoff lblk# inode# )
diroff @ totoff @ lblk# inode#
;
: restore-dirent ( diroff totoff lblk# inode# -- )
set-inode to lblk# totoff ! diroff !
get-dirblk drop
;
\ **** Select the next directory entry
: next-dirent ( -- end? )
dirent-len@ dup diroff +! totoff +!
totoff @ u>d dfile-size d< 0= if true exit then
diroff @ bsize = if
lblk#++ get-dirblk if true exit then
diroff off
then
false
;
\ **** From directory, get name of file
: file-name ( -- adr len )
dirent la1+ wa1+ dup wa1+ ( len-adr name-adr )
swap c@ ( adr len )
;
: +link-count ( increment -- )
\ link-count = 1 means that the directory has more links than can
\ be represented in a 16-bit number; don't increment in that case.
dir? sb-nlink? and if ( increment )
link-count 1 = if ( increment )
drop exit ( -- )
then ( increment )
then ( increment )
link-count + ( link-count' )
\ If the incremented value exceeds the limit, store 1
\ We should also set the RO_COMPAT_DIR_NLINK bit in the superblock,
\ but we assume that OFW won't be used to create enormous directories
dir? sb-nlink? and if ( link-count )
dup d# 65000 >= if ( link-count )
drop 1 ( link-count' )
then ( link-count' )
then ( link-count )
link-count! ( )
;
: new-inode ( mode -- inode# )
alloc-inode set-inode ( mode ) \ alloc-inode erases the inode
file-attr! ( )
time&date >unix-seconds ( time )
dup atime! ( time ) \ set access time
dup ctime! ( time ) \ set creation time
mtime! ( ) \ set modification time
0 link-count! ( ) \ link count will be incremented by new-dirent
dir? if
1 inode# 1- ipg / used-dirs+!
then
inode# ( inode# )
;
\ On entry:
\ inode# refers to the directory block's inode
\ d.dir-block# is the physical block number of the first directory block
\ diroff @ is 0
\ On successful exit:
\ d.dir-block# is the physical block number of the current directory block
\ diroff @ is the within-block offset of the new dirent
: no-dir-space? ( #needed -- true | offset-to-next false )
begin ( #needed )
dirent-inode@ if ( #needed )
dup dirent-len@ dirent-reclen - <= if ( #needed )
\ Carve space out of active dirent
drop ( )
dirent-len@ dirent-reclen - ( offset-to-next )
dirent-reclen dup dirent-len! diroff +! ( offset-to-next )
false exit
then
else ( #needed )
dup dirent-len@ <= if ( #needed )
\ Reuse deleted-but-present dirent
drop ( )
dirent-len@ ( offset-to-next )
false exit
then ( #needed )
then ( #needed )
next-dirent ( #needed )
until ( #needed )
drop true
;
\ a directory entry needs 8+n 4-aligned bytes, where n is the name length
\ the last entry has a larger size; it points to the end of the block
: (last-dirent) ( -- penultimate-offset )
diroff off 0
begin ( last )
dirent-len@ ( last rec-len )
dup diroff @ + bsize < ( last rec-len not-end? )
\ over dirent-reclen = and ( last rec-len not-end? )
while ( last rec-len )
nip diroff @ swap ( last' rec-len )
diroff +! ( last )
repeat ( last )
drop ( last )
;
: last-dirent ( -- free-bytes )
dfile-size bsize um/mod nip swap 0= if 1- then to lblk# ( )
find-dirblk
(last-dirent) drop
dirent-len@ dirent-reclen -
;
0 constant unknown-type
1 constant regular-type
2 constant dir-type
3 constant chrdev-type
4 constant blkdev-type
5 constant fifo-type
6 constant sock-type
7 constant symlink-type
create dir-types
unknown-type c, \ 0
fifo-type c, \ 1
chrdev-type c, \ 2
unknown-type c, \ 3
dir-type c, \ 4
unknown-type c, \ 5
blkdev-type c, \ 6
unknown-type c, \ 7
regular-type c, \ 8
unknown-type c, \ 9
symlink-type c, \ 10
unknown-type c, \ 11
sock-type c, \ 12
unknown-type c, \ 13
unknown-type c, \ 14
unknown-type c, \ 15
: inode>dir-type ( -- dir-type )
filetype d# 12 rshift ( index )
dir-types + c@
;
: fill-dirent ( name$ rec-len inode# -- )
dup set-inode 1 +link-count ( name$ rec-len inode# dir-type )
\ XXX this should be contingent upon EXT2_FEATURE_INCOMPAT_FILETYPE
inode>dir-type dirent-type! ( name$ rec-len inode# )
dirent-inode! ( name$ rec-len )
dirent-len! ( name$ )
dup dirent-namelen! ( name$ )
dirent-nameadr swap move ( )
;
: to-previous-dirent ( -- )
diroff @ ( this )
diroff off ( this )
begin ( this )
dup diroff @ dirent-len@ + <> ( this not-found? )
while ( this )
dirent-len@ diroff +! ( this )
repeat ( this )
diroff @ swap - totoff +! ( )
;
\ Delete the currently selected inode. Does not affect the directory entry, if any.
: idelete ( -- )
dir? if
-1 inode# 1- ipg / used-dirs+!
then
\ Short symlinks hold no blocks, but have a string in the direct block list,
\ so we must not interpret that string as a block list.
d.#blks-held d0<> if
extent? if delete-extents else delete-blocks then
then
\ clear d.#blks-held, link-count, etc.
0 +i /inode 6 /l* /string erase
\ delete inode, and set its deletion time.
time&date >unix-seconds dtime!
inode# free-inode
;
\ delete directory entry at diroff
: dirent-unlink ( -- )
inode# >r
dirent-inode@ set-inode -1 +link-count
\ Release the inode if it has no more links
link-count 0<= if idelete then
diroff @ if
\ Not first dirent in block; coalesce with previous
dirent-len@ ( deleted-len )
to-previous-dirent ( deleted-len )
dirent-len@ + dirent-len! ( )
dirent dirent-reclen + ( adr )
dirent-len@ dirent-reclen - erase ( )
else
\ First dirent in block; zap its inode
0 dirent-inode!
then
r> set-inode
;
\ The argument inode# means the inode to which the new directory entry
\ will refer. The inode of the containing directory is in the *value*
\ named inode#
: new-dirent ( name$ inode# -- error? )
>r ( name$ r: inode# )
\ check for room in the directory, and expand it if necessary
dup >reclen no-dir-space? if ( name$ new-reclen r: inode# )
\ doesn't fit, allocate more room
bsize ( name$ bsize r: inode# )
append-block ( name$ bsize r: inode# )
lblk#++ get-dirblk if ( name$ bsize r: inode# )
r> 4drop ( )
true exit ( -- true )
then ( name$ bsize r: inode# )
then ( name$ rec-len r: inode# )
\ At this point dirent points to the place for the new dirent
r> fill-dirent ( )
false ( error? )
;
: ($create) ( name$ mode -- error? )
new-inode ( name$ inode# )
\ new-inode changed the value of inode#; we must restore it so
\ new-dirent can find info about the containing directory
wd-inum set-inode ( name$ inode# )
new-dirent ( error? )
;
: linkpath ( -- a )
d.file-acl d0<> if bsize 9 rshift else 0 then ( #acl-blocks )
u>d d.#blks-held d<> if \ long symbolic link path
direct0 int@ block
else \ short symbolic link path
direct0
then
;
char \ instance value delimiter
defer $resolve-path
d# 1024 constant /symlink \ Max length of a symbolic link
: set-root ( -- )
root-dir# to wd-inum root-dir# to wf-inum dir-type to wf-type
;
: strip\ ( name$ -- name$' )
dup 0<> if ( name$ )
over c@ delimiter = if ( name$ )
1 /string ( name$ )
set-root ( name$ )
then ( name$ )
then ( name$ )
;
: first-dirent ( dir-inode# -- end? ) \ Adapted from (init-dir)
set-inode
0 to lblk#
get-dirblk if true exit then
diroff off totoff off ( )
false ( )
;
\ On entry:
\ inode# is the inode of the directory file
\ d.dir-block# is the physical block number of the first directory block
\ diroff @ and totoff @ are 0
\ On successful exit:
\ d.dir-block# is the physical block number of the current directory block
\ diroff @ is the within-block offset of the directory entry that matches name$
\ totoff @ is the overall offset of the directory entry that matches name$
: $find-name ( name$ dir-inum -- error? )
first-dirent ( end? )
begin 0= while ( name$ )
\ dirent-inode@ = 0 means a deleted dirent at the beginning
\ of a block; skip those
dirent-inode@ if ( name$ )
2dup file-name ( name$ name$ this-name$ )
$= if
dirent-inode@ to wf-inum ( name$ )
dirent-type@ to wf-type ( name$ )
2drop false exit
then ( name$ )
then
next-dirent ( name$ end? )
repeat ( name$ )
2drop ( )
true
;
: symlink-resolution$ ( inum -- data$ )
set-inode
linkpath dup cstrlen
;
\ The work file is a symlink. Resolve it to a new dirent
: dir-link ( -- error? )
delimiter >r [char] / to delimiter ( r: delim )
\ Allocate temporary space for the symlink value (new name)
/symlink alloc-mem >r ( r: delim dst )
\ Copy the symlink resolution to the temporary buffer
wf-inum symlink-resolution$ ( src len r: delim dst )
tuck r@ swap move ( len r: delim dst )
r@ swap $resolve-path ( error? r: delim dst )
r> /symlink free-mem ( error? r: delim )
r> to delimiter ( error? )
;
\ On successful exit, wf-inum is the inode# of the last path component,
\ wf-type is its type, and wd-inum is inode# of the last directory encountered
: ($resolve-path) ( path$ -- error? )
dir-type to wf-type
\ strip\ sets wd-inum if the path begins with the delimiter
begin strip\ dup while ( path$ )
wf-type case ( path$ c: type )
dir-type of ( path$ )
delimiter left-parse-string ( rem$' head$ )
\ $find-name sets wf-inum and wf-type to the pathname component
wd-inum $find-name if 2drop true exit then ( rem$ )
wf-type dir-type = if ( rem$ )
wf-inum to wd-inum ( rem$ )
then ( rem$ )
endof ( rem$ )
symlink-type of ( rem$ )
\ dir-link recursively calls $resolve-path, setting
\ wf-inum and wf-type to the symlink's last component
dir-link if 2drop true exit then ( rem$ )
endof ( rem$ )
( default ) ( rem$ c: type )
\ The parent is an ordinary file or something else that
\ can't be treated as a directory
3drop true exit
endcase ( rem$ )
repeat ( rem$ )
2drop false ( false )
;
' ($resolve-path) to $resolve-path
: $find-file ( name$ -- error? )
$resolve-path if true exit then ( )
begin
\ We now have the dirent for the file at the end of the string
wf-type case
dir-type of wf-inum to wd-inum false exit endof \ Directory
regular-type of false exit endof \ Regular file
symlink-type of dir-link if true exit then endof \ Link
( default ) \ Anything else (special file) is error
drop true exit
endcase
again
;
\ --
: $chdir ( path$ -- error? )
$find-file if true exit then
wf-type dir-type <> if true exit then
wd-inum first-dirent
;
\ Returns true if inode# refers to a directory that is empty
\ Side effect - changes dirent context
: empty-dir? ( inode# -- empty-dir? )
set-inode
dir? 0= if false exit then
inode# first-dirent if false exit then \ Should be pointing to "." entry
next-dirent if false exit then \ Should be point to ".." entry
next-dirent ( end? ) \ The rest should be empty
;
external
\ directory information
: file-info ( id -- false | id' s m h d m y len attributes name$ true )
inode# >r dirent-inode@ set-inode ( id )
1+ mtime unix-seconds> dfile-size drop file-attr file-name true
r> set-inode
;
\ Deleted files at the beginning of a directory block have inode=0
: next-file-info ( id -- false | id' s m h d m y len attributes name$ true )
dup if
begin
next-dirent 0= while
dirent-inode@ if file-info exit then
repeat
drop false
else
file-info
then
;
: $readlink ( name$ -- true | link$ false )
dirent-vars 2>r 2>r
$resolve-path if 2r> 2r> restore-dirent true exit then
wf-type symlink-type <> if 2r> 2r> restore-dirent true exit then
wf-inum symlink-resolution$ false
2r> 2r> restore-dirent
;
headers
\ LICENSE_BEGIN
\ Copyright (c) 2006 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