unit vfs_init;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 sysutils,
 murmurhash,
 mqueue,
 vmount;

function vfs_byname(name:PChar):p_vfsconf;
function vfs_byname_kld(fstype:PChar;error:PInteger):p_vfsconf;
function vfs_register(vfc:p_vfsconf):Integer;
function vfs_unregister(vfc:p_vfsconf):Integer;

implementation

uses
 errno,
 vfs_default;

var
 maxvfsconf:Integer=VFS_GENERIC+1;

{
 * Single-linked list of configured VFSes.
 * New entries are added/deleted by vfs_register()/vfs_unregister()
 }
 vfsconf:TAILQ_HEAD=(tqh_first:nil;tqh_last:@vfsconf.tqh_first);

{
 * Loader.conf variable vfs.typenumhash enables setting vfc_typenum using a hash
 * calculation on vfc_name, so that it doesn't change when file systems are
 * loaded in a different order. This will avoid the NFS server file handles from
 * changing for file systems that use vfc_typenum in their fsid.
 }
 vfs_typenumhash:Integer=1;

{
 * vfs_init.c
 *
 * Allocate and fill in operations vectors.
 *
 * An undocumented feature of this approach to defining operations is that
 * there can be multiple entries in vfs_opv_descs for the same operations
 * vector. This allows third parties to extend the set of operations
 * supported by another layer in a binary compatibile way. For example,
 * assume that NFS needed to be modified to support Ficus. NFS has an entry
 * (probably nfs_vnopdeop_decls) declaring all the operations NFS supports by
 * default. Ficus could add another entry (ficus_nfs_vnodeop_decl_entensions)
 * listing those new operations Ficus adds to NFS, all without modifying the
 * NFS code. (Of couse, the OTW NFS protocol still needs to be munged, but
 * that is a(whole)nother story.) This is a feature.
 }

{
 * Routines having to do with the management of the vnode table.
 }
function vfs_byname(name:PChar):p_vfsconf;
var
 vfsp:p_vfsconf;
begin
 Result:=nil;
 vfsp:=TAILQ_FIRST(@vfsconf);
 while (vfsp<>nil) do
 begin
  if (strcomp(name, vfsp^.vfc_name)=0) then Exit(vfsp);
  vfsp:=TAILQ_NEXT(vfsp,@vfsp^.vfc_list);
 end;
end;

function vfs_byname_kld(fstype:PChar;error:PInteger):p_vfsconf;
var
 vfsp:p_vfsconf;
 fileid,loaded:Integer;
begin
 vfsp:=vfs_byname(fstype);
 if (vfsp<>nil) then Exit(vfsp);

 error^:=ENODEV;

 {
  { Try to load the respective module. }
  error^:=kern_kldload(td, fstype, @fileid);

  loaded:=(error^=0);

  if (error^=EEXIST)
   error^:=0;

  if (error^<>0) then
   Exit(nil);

  { Look up again to see if the VFS was loaded. }
  vfsp:=vfs_byname(fstype);
  if (vfsp=nil)
  begin
   if (loaded<>0) then
    kern_kldunload(td, fileid, LINKER_UNLOAD_FORCE);

   error^:=ENODEV;
   Exit(nil);
  end;
 }

 Exit(vfsp);
end;

{ Register a new filesystem type in the global table }
function vfs_register(vfc:p_vfsconf):Integer;
var
 vfsops:p_vfsops;
 tvfc:p_vfsconf;
 hashval:DWORD;
 secondpass:Integer;
begin
 //struct sysctl_oid *oidp;

 if (vfc^.vfc_version<>VFS_VERSION) then
 begin
  Assert(false,'ERROR: filesystem %s, unsupported ABI version');
  Exit(EINVAL);
 end;

 if (vfs_byname(vfc^.vfc_name)<>nil) then
  Exit(EEXIST);

 if (vfs_typenumhash<>0) then
 begin
  {
   * Calculate a hash on vfc_name to use for vfc_typenum. Unless
   * all of 1<^.255 are assigned, it is limited to 8bits since
   * that is what ZFS uses from vfc_typenum and is also the
   * preferred range for vfs_getnewfsid().
   }
  hashval:=MurmurHash64A(@vfc^.vfc_name,strlen(vfc^.vfc_name),0);
  hashval:=hashval and $ff;
  secondpass:=0;
  repeat
   { Look for and fix any collision. }

   tvfc:=TAILQ_FIRST(@vfsconf);
   while (tvfc<>nil) do
   begin
    if (hashval=tvfc^.vfc_typenum) then
    begin
     if (hashval=255) and (secondpass=0) then
     begin
      hashval:=1;
      secondpass:=1;
     end else
      Inc(hashval);
     break;
    end;

    tvfc:=TAILQ_NEXT(tvfc,@tvfc^.vfc_list);
   end;
  until (tvfc=nil);
  vfc^.vfc_typenum:=hashval;
  if (vfc^.vfc_typenum >= maxvfsconf) then
   maxvfsconf:=vfc^.vfc_typenum + 1;
 end else
 begin
  Inc(maxvfsconf);
  vfc^.vfc_typenum:=maxvfsconf;
 end;
 TAILQ_INSERT_TAIL(@vfsconf,vfc,@vfc^.vfc_list);

 {
  * If this filesystem has a sysctl node under vfs
  * (i.e. vfs.xxfs), then change the oid number of that node to
  * match the filesystem's type number.  This allows user code
  * which uses the type number to read sysctl variables defined
  * by the filesystem to continue working. Since the oids are
  * in a sorted list, we need to make sure the order is
  * preserved by re-registering the oid after modifying its
  * number.
  }
 //sysctl_lock();
 //SLIST_FOREACH(oidp, @sysctl__vfs_children, oid_link)
 // if (strcmp(oidp^.oid_name, vfc^.vfc_name)=0) begin
 //  sysctl_unregister_oid(oidp);
 //  oidp^.oid_number:=vfc^.vfc_typenum;
 //  sysctl_register_oid(oidp);
 //  break;
 // end;
 //sysctl_unlock();

 {
  * Initialise unused ``struct vfsops'' fields, to use
  * the vfs_std*() functions.  Note, we need the mount
  * and unmount operations, at the least.  The check
  * for vfsops available is just a debugging aid.
  }
 Assert(vfc^.vfc_vfsops<>nil,'Filesystem %s has no vfsops');
 {
  * Check the mount and unmount operations.
  }
 vfsops:=vfc^.vfc_vfsops;

 Assert(vfsops^.vfs_mount<>nil,'Filesystem %s has no mount op');
 Assert(vfsops^.vfs_unmount<>nil,'Filesystem %s has no unmount op');

 if (vfsops^.vfs_root=nil) then
  { Exit file system's root vnode }
  vfsops^.vfs_root:=@vfs_stdroot;
 if (vfsops^.vfs_quotactl=nil) then
  { quota control }
  vfsops^.vfs_quotactl:=@vfs_stdquotactl;
 if (vfsops^.vfs_statfs=nil) then
  { Exit file system's status }
  vfsops^.vfs_statfs:=@vfs_stdstatfs;
 if (vfsops^.vfs_sync=nil) then
  {
   * flush unwritten data (nosync)
   * file systems can use vfs_stdsync
   * explicitly by setting it in the
   * vfsop vector.
   }
  vfsops^.vfs_sync:=@vfs_stdnosync;
 if (vfsops^.vfs_vget=nil) then
  { convert an inode number to a vnode }
  vfsops^.vfs_vget:=@vfs_stdvget;
 if (vfsops^.vfs_fhtovp=nil) then
  { turn an NFS file handle into a vnode }
  vfsops^.vfs_fhtovp:=@vfs_stdfhtovp;
 //if (vfsops^.vfs_checkexp=nil) then
 // { check if file system is exported }
 // vfsops^.vfs_checkexp:=@vfs_stdcheckexp;
 if (vfsops^.vfs_init=nil) then
  { file system specific initialisation }
  vfsops^.vfs_init:=@vfs_stdinit;
 if (vfsops^.vfs_uninit=nil) then
  { file system specific uninitialisation }
  vfsops^.vfs_uninit:=@vfs_stduninit;
 if (vfsops^.vfs_extattrctl=nil) then
  { extended attribute control }
  vfsops^.vfs_extattrctl:=@vfs_stdextattrctl;
 if (vfsops^.vfs_sysctl=nil) then
  vfsops^.vfs_sysctl:=@vfs_stdsysctl;

 {
  * Call init function for this VFS...
  }
 vfc^.vfc_vfsops^.vfs_init(vfc);

 Result:=0;
end;


{ Remove registration of a filesystem type }
function vfs_unregister(vfc:p_vfsconf):Integer;
var
 vfsp:p_vfsconf;
 error,maxtypenum:Integer;
begin
 vfsp:=vfs_byname(vfc^.vfc_name);
 if (vfsp=nil) then
  Exit(EINVAL);
 if (vfsp^.vfc_refcount<>0) then
  Exit(EBUSY);
 if (vfc^.vfc_vfsops^.vfs_uninit<>nil) then
 begin
  error:=vfc^.vfc_vfsops^.vfs_uninit(vfsp);
  if (error<>0) then
   Exit(error);
 end;
 TAILQ_REMOVE(@vfsconf,vfsp,@vfsp^.vfc_list);
 maxtypenum:=VFS_GENERIC;

 vfsp:=TAILQ_FIRST(@vfsconf);
 while (vfsp<>nil) do
 begin
  if (maxtypenum<vfsp^.vfc_typenum) then
   maxtypenum:=vfsp^.vfc_typenum;

  vfsp:=TAILQ_NEXT(vfsp,@vfsp^.vfc_list);
 end;

 maxvfsconf:=maxtypenum + 1;
 Result:=0;
end;

{
 * Standard kernel module handling code for filesystem modules.
 * Referenced from VFS_SET().
 }
{
int
vfs_modevent(module_t mod, int type, void *data)
begin
 p_vfsconf vfc;
 int error:=0;

 vfc:=(p_vfsconf )data;

 switch (type) begin
 case MOD_LOAD:
  if (vfc)
   error:=vfs_register(vfc);
  break;

 case MOD_UNLOAD:
  if (vfc)
   error:=vfs_unregister(vfc);
  break;
 default:
  error:=EOPNOTSUPP;
  break;
 end;
 Exit (error);
end;
}

end.

