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.