FPPS4/sys/fs/devfs/devfs_devs.pas

715 lines
16 KiB
Plaintext

unit devfs_devs;
{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}
interface
uses
mqueue,
kern_id,
sys_conf,
devfs_int,
devfs;
function devfs_alloc(flags:Integer):p_cdev;
function devfs_dev_exists(name:PChar):Integer;
procedure devfs_free(cdev:p_cdev);
function devfs_find(dd:p_devfs_dirent;name:PChar;namelen:Integer;_type:Integer):p_devfs_dirent;
function devfs_newdirent(name:PChar;namelen:Integer):p_devfs_dirent;
function devfs_parent_dirent(de:p_devfs_dirent):p_devfs_dirent;
function devfs_vmkdir(dmp:p_devfs_mount;name:PChar;namelen:Integer;dotdot:p_devfs_dirent;inode:DWORD):p_devfs_dirent;
procedure devfs_dirent_free(de:p_devfs_dirent);
procedure devfs_rmdir_empty(dm:p_devfs_mount;de:p_devfs_dirent);
procedure devfs_delete(dm:p_devfs_mount;de:p_devfs_dirent;flags:Integer);
procedure devfs_purge(dm:p_devfs_mount;dd:p_devfs_dirent);
procedure devfs_metoo(cdp:p_cdev_priv;dm:p_devfs_mount);
function devfs_populate_loop(dm:p_devfs_mount;cleanup:Integer):Integer;
procedure devfs_populate(dm:p_devfs_mount);
procedure devfs_cleanup(dm:p_devfs_mount);
procedure devfs_create(dev:p_cdev);
procedure devfs_destroy(dev:p_cdev);
function devfs_alloc_cdp_inode():ino_t;
procedure devfs_free_cdp_inode(ino:ino_t);
procedure devfs_devs_init(); //SYSINIT(devfs_devs, SI_SUB_DEVFS, SI_ORDER_FIRST, devfs_devs_init, NULL);
implementation
uses
time,
vdirent,
vnode,
kern_mtx,
kern_sx,
vfs_vnops,
vfs_subr,
vnode_if;
var
devfs_desc:t_id_desc=(free:nil;refs:0);
devfs_inos:t_id_desc_table;
//
function cdev2priv(c:Pointer):p_cdev_priv; inline;
begin
Result:=c-ptruint(@p_cdev_priv(nil)^.cdp_c);
end;
function devfs_alloc(flags:Integer):p_cdev; public;
var
cdp:p_cdev_priv;
cdev:p_cdev;
ts:timespec;
begin
cdp:=AllocMem(SizeOf(t_cdev_priv));
if (cdp=nil) then
Exit(nil);
cdp^.cdp_dirents:=@cdp^.cdp_dirent0;
cdp^.cdp_dirent0:=nil;
cdp^.cdp_maxdirent:=0;
cdp^.cdp_inode:=0;
cdev:=@cdp^.cdp_c;
cdev^.si_name:=cdev^.__si_namebuf;
LIST_INIT(@cdev^.si_children);
vfs_timestamp(@ts);
cdev^.si_atime:=ts;
cdev^.si_mtime:=ts;
cdev^.si_ctime:=ts;
Exit(cdev);
end;
function devfs_dev_exists(name:PChar):Integer; public;
var
cdp:p_cdev_priv;
begin
mtx_assert(devmtx);
cdp:=TAILQ_FIRST(@cdevp_list);
while (cdp<>nil) do
begin
if ((cdp^.cdp_flags and CDP_ACTIVE)=0) then
begin
cdp:=TAILQ_NEXT(cdp,@cdp^.cdp_list);
continue;
end;
if (devfs_pathpath(cdp^.cdp_c.si_name, name)<>0) then
Exit(1);
if (devfs_pathpath(name, cdp^.cdp_c.si_name)<>0) then
Exit(1);
//
cdp:=TAILQ_NEXT(cdp,@cdp^.cdp_list);
end;
if (devfs_dir_find(name)<>0) then
Exit(1);
Exit(0);
end;
procedure devfs_free(cdev:p_cdev); public;
var
cdp:p_cdev_priv;
begin
cdp:=cdev2priv(cdev);
devfs_free_cdp_inode(cdp^.cdp_inode);
if (cdp^.cdp_maxdirent > 0) then
FreeMem(cdp^.cdp_dirents);
FreeMem(cdp);
end;
function devfs_find(dd:p_devfs_dirent;name:PChar;namelen:Integer;_type:Integer):p_devfs_dirent; public;
var
de:p_devfs_dirent;
begin
de:=TAILQ_FIRST(@dd^.de_dlist);
while (de<>nil) do
begin
if (namelen<>de^.de_dirent^.d_namlen) then
begin
de:=TAILQ_NEXT(de,@de^.de_list);
continue;
end;
if (_type<>0) and (_type<>de^.de_dirent^.d_type) then
begin
de:=TAILQ_NEXT(de,@de^.de_list);
continue;
end;
if (CompareByte(name^, de^.de_dirent^.d_name, namelen)<>0) then
begin
de:=TAILQ_NEXT(de,@de^.de_list);
continue;
end;
break;
end;
if (de<>nil) then
begin
Assert((de^.de_flags and DE_DOOMED)=0,'devfs_find: Exiting a doomed entry');
end;
Exit(de);
end;
function devfs_newdirent(name:PChar;namelen:Integer):p_devfs_dirent; public;
var
i:Integer;
de:p_devfs_dirent;
d:t_dirent;
begin
d.d_namlen:=namelen;
i:=sizeof(t_devfs_dirent) + GENERIC_DIRSIZ(@d);
de:=AllocMem(i);
de^.de_dirent:=p_dirent(de + 1);
de^.de_dirent^.d_namlen:=namelen;
de^.de_dirent^.d_reclen:=GENERIC_DIRSIZ(@d);
Move(name^, de^.de_dirent^.d_name, namelen);
de^.de_dirent^.d_name[namelen]:=#0;
vfs_timestamp(@de^.de_ctime);
de^.de_mtime :=de^.de_ctime;
de^.de_atime :=de^.de_ctime;
de^.de_links :=1;
de^.de_holdcnt:=1;
//mac_devfs_init(de);
Exit(de);
end;
function devfs_parent_dirent(de:p_devfs_dirent):p_devfs_dirent; public;
begin
if (de^.de_dirent^.d_type<>DT_DIR) then
begin
Exit(de^.de_dir);
end;
if ((de^.de_flags and (DE_DOT or DE_DOTDOT))<>0) then
begin
Exit(nil);
end;
de:=TAILQ_FIRST(@de^.de_dlist); { '.' }
if (de=nil) then Exit(nil);
de:=TAILQ_NEXT(de,@de^.de_list); { '..' }
if (de=nil) then Exit(nil);
Exit(de^.de_dir);
end;
function devfs_vmkdir(dmp:p_devfs_mount;name:PChar;namelen:Integer;dotdot:p_devfs_dirent;inode:DWORD):p_devfs_dirent; public;
var
dd,de:p_devfs_dirent;
begin
{ Create the new directory }
dd:=devfs_newdirent(name, namelen);
TAILQ_INIT(@dd^.de_dlist);
dd^.de_dirent^.d_type:=DT_DIR;
dd^.de_mode :=&0555;
dd^.de_links:=2;
dd^.de_dir :=dd;
if (inode<>0) then
dd^.de_inode:=inode
else
dd^.de_inode:=devfs_alloc_cdp_inode;
{
* '.' and '..' are always the two first entries in the
* de_dlist list.
*
* Create the '.' entry in the new directory.
}
de:=devfs_newdirent('.', 1);
de^.de_dirent^.d_type:=DT_DIR;
de^.de_flags:=de^.de_flags or DE_DOT;
TAILQ_INSERT_TAIL(@dd^.de_dlist,de,@de^.de_list);
de^.de_dir:=dd;
{ Create the '..' entry in the new directory. }
de:=devfs_newdirent('..', 2);
de^.de_dirent^.d_type:=DT_DIR;
de^.de_flags:=de^.de_flags or DE_DOTDOT;
TAILQ_INSERT_TAIL(@dd^.de_dlist,de,@de^.de_list);
if (dotdot=nil) then
begin
de^.de_dir:=dd;
end else
begin
de^.de_dir:=dotdot;
sx_assert(@dmp^.dm_lock);
TAILQ_INSERT_TAIL(@dotdot^.de_dlist,dd,@dd^.de_list);
Inc(dotdot^.de_links);
devfs_rules_apply(dmp, dd);
end;
//mac_devfs_create_directory(dmp^.dm_mount, name, namelen, dd);
Exit(dd);
end;
procedure devfs_dirent_free(de:p_devfs_dirent); public;
begin
FreeMem(de);
end;
{
* Removes a directory if it is empty. Also empty parent directories are
* removed recursively.
}
procedure devfs_rmdir_empty(dm:p_devfs_mount;de:p_devfs_dirent); public;
var
dd,de_dot,de_dotdot:p_devfs_dirent;
begin
sx_assert(@dm^.dm_lock);
repeat
Assert(de^.de_dirent^.d_type=DT_DIR,'devfs_rmdir_empty: de is not a directory');
if ((de^.de_flags and DE_DOOMED)<>0) or (de=dm^.dm_rootdir) then
Exit;
de_dot:=TAILQ_FIRST(@de^.de_dlist);
Assert(de_dot<>nil, 'devfs_rmdir_empty: . missing');
de_dotdot:=TAILQ_NEXT(de_dot,@de_dot^.de_list);
Assert(de_dotdot<>nil, 'devfs_rmdir_empty: .. missing');
{ Exit if the directory is not empty. }
if (TAILQ_NEXT(de_dotdot,@de_dotdot^.de_list)<>nil) then
begin
Exit;
end;
dd:=devfs_parent_dirent(de);
Assert(dd<>nil, 'devfs_rmdir_empty: nil dd');
TAILQ_REMOVE(@de^.de_dlist,de_dot,@de_dot^.de_list);
TAILQ_REMOVE(@de^.de_dlist,de_dotdot,@de_dotdot^.de_list);
TAILQ_REMOVE(@dd^.de_dlist,de,@de^.de_list);
DEVFS_DE_HOLD(dd);
devfs_delete(dm, de ,DEVFS_DEL_NORECURSE);
devfs_delete(dm, de_dot ,DEVFS_DEL_NORECURSE);
devfs_delete(dm, de_dotdot,DEVFS_DEL_NORECURSE);
if (DEVFS_DE_DROP(dd)) then
begin
devfs_dirent_free(dd);
Exit;
end;
de:=dd;
until false;
end;
{
* The caller needs to hold the dm for the duration of the call since
* dm^.dm_lock may be temporary dropped.
}
procedure devfs_delete(dm:p_devfs_mount;de:p_devfs_dirent;flags:Integer); public;
var
dd:p_devfs_dirent;
vp:p_vnode;
begin
Assert((de^.de_flags and DE_DOOMED)=0,'devfs_delete doomed dirent');
de^.de_flags:=de^.de_flags or DE_DOOMED;
if ((flags and DEVFS_DEL_NORECURSE)=0) then
begin
dd:=devfs_parent_dirent(de);
if (dd<>nil) then
begin
DEVFS_DE_HOLD(dd);
end;
if (de^.de_flags and DE_USER)<>0 then
begin
Assert(dd<>nil,'devfs_delete: nil dd');
devfs_dir_unref_de(dm, dd);
end;
end else
begin
dd:=nil;
end;
mtx_lock(devfs_de_interlock);
vp:=de^.de_vnode;
if (vp<>nil) then
begin
VI_LOCK(vp);
mtx_unlock(devfs_de_interlock);
vholdl(vp);
sx_unlock(@dm^.dm_lock);
if ((flags and DEVFS_DEL_VNLOCKED)=0) then
vn_lock(vp, LK_EXCLUSIVE or LK_INTERLOCK or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%})
else
VI_UNLOCK(vp);
vgone(vp);
if ((flags and DEVFS_DEL_VNLOCKED)=0) then
begin
VOP_UNLOCK(vp, 0);
end;
vdrop(vp);
sx_xlock(@dm^.dm_lock);
end else
begin
mtx_unlock(devfs_de_interlock);
end;
if (de^.de_symlink<>nil) then
begin
FreeMem(de^.de_symlink);
de^.de_symlink:=nil;
end;
//mac_devfs_destroy(de);
if (de^.de_inode > DEVFS_ROOTINO) then
begin
devfs_free_cdp_inode(de^.de_inode);
de^.de_inode:=0;
end;
if DEVFS_DE_DROP(de) then
begin
devfs_dirent_free(de);
end;
if (dd<>nil) then
begin
if DEVFS_DE_DROP(dd) then
devfs_dirent_free(dd)
else
devfs_rmdir_empty(dm, dd);
end;
end;
{
* Called on unmount.
* Recursively removes the entire tree.
* The caller needs to hold the dm for the duration of the call.
}
procedure devfs_purge(dm:p_devfs_mount;dd:p_devfs_dirent); public;
var
de:p_devfs_dirent;
begin
if (dm=nil) or (dd=nil) then Exit;
sx_assert(@dm^.dm_lock);
DEVFS_DE_HOLD(dd);
repeat
{
* Use TAILQ_LAST() to remove '.' and '..' last.
* We might need '..' to resolve a path in
* devfs_dir_unref_de().
}
de:=TAILQ_LAST(@dd^.de_dlist);
if (de=nil) then break;
TAILQ_REMOVE(@dd^.de_dlist,de,@de^.de_list);
if ((de^.de_flags and DE_USER)<>0) then
devfs_dir_unref_de(dm, dd);
if ((de^.de_flags and (DE_DOT or DE_DOTDOT))<>0) then
devfs_delete(dm, de, DEVFS_DEL_NORECURSE)
else
if (de^.de_dirent^.d_type=DT_DIR) then
devfs_purge(dm, de)
else
devfs_delete(dm, de, DEVFS_DEL_NORECURSE);
until false;
if DEVFS_DE_DROP(dd) then
devfs_dirent_free(dd)
else
if ((dd^.de_flags and DE_DOOMED)=0) then
devfs_delete(dm, dd, DEVFS_DEL_NORECURSE);
end;
{
* Each cdev_priv has an array of pointers to devfs_dirent which is indexed
* by the mount points dm_idx.
* This function extends the array when necessary, taking into account that
* the default array is 1 element and not malloc'ed.
}
procedure devfs_metoo(cdp:p_cdev_priv;dm:p_devfs_mount); public;
var
dep:pp_devfs_dirent;
siz:Integer;
begin
siz:=(dm^.dm_idx + 1) * sizeof(Pointer);
dep:=AllocMem(siz);
dev_lock();
if (dm^.dm_idx <= cdp^.cdp_maxdirent) then
begin
{ We got raced }
dev_unlock();
FreeMem(dep);
Exit;
end;
Move(cdp^.cdp_dirents^,dep^,(cdp^.cdp_maxdirent + 1)*SizeOf(Pointer));
if (cdp^.cdp_maxdirent > 0) then
begin
FreeMem(cdp^.cdp_dirents);
end;
cdp^.cdp_dirents:=dep;
{
* XXX: if malloc told us how much we actually got this could
* XXX: be optimized.
}
cdp^.cdp_maxdirent:=dm^.dm_idx;
dev_unlock();
end;
{
* The caller needs to hold the dm for the duration of the call.
}
function devfs_populate_loop(dm:p_devfs_mount;cleanup:Integer):Integer; public;
var
cdp:p_cdev_priv;
de:p_devfs_dirent;
dd:p_devfs_dirent;
pdev:p_cdev;
de_flags,j:Integer;
q,s:PChar;
begin
sx_assert(@dm^.dm_lock);
dev_lock();
cdp:=TAILQ_FIRST(@cdevp_list);
while (cdp<>nil) do
begin
Assert(cdp^.cdp_dirents<>nil,'nil cdp_dirents');
{
* If we are unmounting, or the device has been destroyed,
* clean up our dirent.
}
if ((cleanup<>0) or
((cdp^.cdp_flags and CDP_ACTIVE)=0)) and
(dm^.dm_idx <= cdp^.cdp_maxdirent) and
(cdp^.cdp_dirents[dm^.dm_idx]<>nil) then
begin
de:=cdp^.cdp_dirents[dm^.dm_idx];
cdp^.cdp_dirents[dm^.dm_idx]:=nil;
Assert(cdp=de^.de_cdp,cdp^.cdp_c.si_name);
Assert(de^.de_dir<>nil,'nil de^.de_dir');
dev_unlock();
TAILQ_REMOVE(@de^.de_dir^.de_dlist,de,@de^.de_list);
de^.de_cdp :=nil;
de^.de_inode:=0;
devfs_delete(dm, de, 0);
dev_lock();
Dec(cdp^.cdp_inuse);
dev_unlock();
Exit(1);
end;
{
* GC any lingering devices
}
if ((cdp^.cdp_flags and CDP_ACTIVE)=0) then
begin
if (cdp^.cdp_inuse > 0) then
begin
cdp:=TAILQ_NEXT(cdp,@cdp^.cdp_list);
continue;
end;
TAILQ_REMOVE(@cdevp_list,cdp,@cdp^.cdp_list);
dev_unlock();
dev_rel(@cdp^.cdp_c);
Exit(1);
end;
{
* Don't create any new dirents if we are unmounting
}
if (cleanup<>0) then
begin
cdp:=TAILQ_NEXT(cdp,@cdp^.cdp_list);
continue;
end;
Assert((cdp^.cdp_flags and CDP_ACTIVE)<>0,'Bogons, I tell ya!');
if (dm^.dm_idx <= cdp^.cdp_maxdirent) and
(cdp^.cdp_dirents[dm^.dm_idx]<>nil) then
begin
de:=cdp^.cdp_dirents[dm^.dm_idx];
Assert(cdp=de^.de_cdp, 'inconsistent cdp');
cdp:=TAILQ_NEXT(cdp,@cdp^.cdp_list);
continue;
end;
Inc(cdp^.cdp_inuse);
dev_unlock();
if (dm^.dm_idx > cdp^.cdp_maxdirent) then
begin
devfs_metoo(cdp, dm);
end;
dd:=dm^.dm_rootdir;
s:=cdp^.cdp_c.si_name;
repeat
q:=s;
while (q^<>'/') and (q^<>#0) do Inc(q);
if (q^<>'/') then break;
de:=devfs_find(dd, s, q - s, 0);
if (de=nil) then
begin
de:=devfs_vmkdir(dm, s, q - s, dd, 0);
end else
if (de^.de_dirent^.d_type=DT_LNK) then
begin
de:=devfs_find(dd, s, q - s, DT_DIR);
if (de=nil) then
begin
de:=devfs_vmkdir(dm, s, q - s, dd, 0);
end;
de^.de_flags:=de^.de_flags or DE_COVERED;
end;
s:=q + 1;
dd:=de;
Assert((dd^.de_dirent^.d_type=DT_DIR) and ((dd^.de_flags and (DE_DOT or DE_DOTDOT))=0),'invalid directory');
until false;
de_flags:=0;
de:=devfs_find(dd, s, q - s, DT_LNK);
if (de<>nil) then
de_flags:=de_flags or DE_COVERED;
de:=devfs_newdirent(s, q - s);
if ((cdp^.cdp_c.si_flags and SI_ALIAS)<>0) then
begin
de^.de_uid :=0;
de^.de_gid :=0;
de^.de_mode:=&0755;
de^.de_dirent^.d_type:=DT_LNK;
pdev:=cdp^.cdp_c.si_parent;
j:=strlen(pdev^.si_name) + 1;
de^.de_symlink:=AllocMem(j);
Move(pdev^.si_name^,de^.de_symlink^,j);
end else
begin
de^.de_uid :=cdp^.cdp_c.si_uid;
de^.de_gid :=cdp^.cdp_c.si_gid;
de^.de_mode:=cdp^.cdp_c.si_mode;
de^.de_dirent^.d_type:=DT_CHR;
end;
de^.de_flags:=de^.de_flags or de_flags;
de^.de_inode:=cdp^.cdp_inode;
de^.de_cdp :=cdp;
//mac_devfs_create_device(cdp^.cdp_c.si_cred, dm^.dm_mount, @cdp^.cdp_c, de);
de^.de_dir:=dd;
TAILQ_INSERT_TAIL(@dd^.de_dlist,de,@de^.de_list);
devfs_rules_apply(dm, de);
dev_lock();
{ XXX: could check that cdp is still active here }
Assert(cdp^.cdp_dirents[dm^.dm_idx]=nil);
cdp^.cdp_dirents[dm^.dm_idx]:=de;
Assert(de^.de_cdp<>Pointer(ptruint($deadc0de)));
dev_unlock();
Exit(1);
end;
dev_unlock();
Exit(0);
end;
{
* The caller needs to hold the dm for the duration of the call.
}
procedure devfs_populate(dm:p_devfs_mount); public;
var
gen:DWORD;
begin
sx_assert(@dm^.dm_lock);
gen:=devfs_generation;
if (dm^.dm_generation=gen) then
begin
Exit;
end;
while (devfs_populate_loop(dm, 0)<>0) do;
dm^.dm_generation:=gen;
end;
{
* The caller needs to hold the dm for the duration of the call.
}
procedure devfs_cleanup(dm:p_devfs_mount); public;
begin
sx_assert(@dm^.dm_lock);
while (devfs_populate_loop(dm, 1)<>0) do;
devfs_purge(dm, dm^.dm_rootdir);
end;
{
* devfs_create() and devfs_destroy() are called from kern_conf.c and
* in both cases the devlock() mutex is held, so no further locking
* is necesary and no sleeping allowed.
}
procedure devfs_create(dev:p_cdev); public;
var
cdp:p_cdev_priv;
begin
mtx_assert(devmtx);
cdp:=cdev2priv(dev);
cdp^.cdp_flags:=cdp^.cdp_flags or CDP_ACTIVE;
cdp^.cdp_inode:=devfs_alloc_cdp_inode;
dev_refl(dev);
TAILQ_INSERT_TAIL(@cdevp_list,cdp,@cdp^.cdp_list);
Inc(devfs_generation);
end;
procedure devfs_destroy(dev:p_cdev); public;
var
cdp:p_cdev_priv;
begin
mtx_assert(devmtx);
cdp:=cdev2priv(dev);
cdp^.cdp_flags:=cdp^.cdp_flags and (not CDP_ACTIVE);
Inc(devfs_generation);
end;
function devfs_alloc_cdp_inode():ino_t; public;
begin
if id_new(@devfs_inos,@devfs_desc,@Result) then
begin
id_release(@devfs_desc); //<-id_new
end else
begin
Result:=-1;
end;
end;
procedure devfs_free_cdp_inode(ino:ino_t); public;
begin
if (ino>0) then
begin
id_del(@devfs_inos,ino,nil);
end;
end;
procedure devfs_devs_init();
begin
id_table_init(@devfs_inos,DEVFS_ROOTINO + 1);
end;
end.