// Copyright 2021 Aaron Hance // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. { Driver->Storage->FDTable - Per-process file descriptor table. Each process owns a TFDTable (allocated on process create, freed on reap). File handles are 1-based indices into the table (0 = invalid). The VFS layer calls fd_alloc / fd_get / fd_close instead of touching a global OpenFiles array. @author(Aaron Hance ) } unit driver.storage.fdtable; interface uses memory.heap, driver.storage.types, core.util, arch.x86.util; const MAX_FDS = 64; type PFileDescriptor = ^TFileDescriptor; TFileDescriptor = record InUse : boolean; AsyncRefs : uint32; { in-flight async ops using this descriptor } Volume : PStorage_Volume; Directory : pchar; { directory path within the volume } FileName : pchar; { filename within that directory } VFSDir : pchar; { full VFS parent directory path, e.g. '/sys' } OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency } DataSize : uint32; { cached file size in bytes (from fileSizeCallback) } DeviceOps : pointer; { PVFSDeviceOps — non-nil for device FDs } DeviceData : pointer; { opaque data passed to device callbacks } FSPrivate : pointer; { opaque FS-level per-file metadata (from openFileCallback) } FSCloseHook : pointer; { procedure(pointer) — frees FSPrivate on close } end; PFDTable = ^TFDTable; TFDTable = record Entries : array[0..MAX_FDS-1] of TFileDescriptor; end; { Allocate and zero-initialise a new FD table on the heap. Called once per process at creation time. Returns nil on out-of-memory. } function fd_table_new : PFDTable; { Free all open descriptors in the table, then free the table itself. Called when a process is reaped. Safe to pass nil. } procedure fd_table_free(table : PFDTable); { Find the first free slot in the table. Returns 1-based handle (1..MAX_FDS) on success, 0 if table is full. } function fd_alloc(table : PFDTable) : uint32; { Get a pointer to the descriptor for a 1-based handle. Returns nil if handle is out of range or the slot is not in use. } function fd_get(table : PFDTable; handle : uint32) : PFileDescriptor; function fd_begin_async(entry : PFileDescriptor) : boolean; procedure fd_end_async(entry : PFileDescriptor); function fd_has_async(entry : PFileDescriptor) : boolean; { Close a single file descriptor: free its buffers and core.strings, mark the slot as not-in-use. Returns true if the handle was valid and open, false otherwise. } function fd_close(table : PFDTable; handle : uint32) : boolean; { Close ALL open file descriptors in the table. Used during process cleanup. } procedure fd_close_all(table : PFDTable); { Close all file descriptors in the table that reference a specific volume. Called when a volume is removed to prevent dangling pointers. } procedure fd_close_for_volume(table : PFDTable; vol : PStorage_Volume); implementation function fd_table_new : PFDTable; var tbl : PFDTable; begin tbl := PFDTable(kalloc(SizeOf(TFDTable))); if tbl <> nil then memset(uint32(tbl), 0, SizeOf(TFDTable)); fd_table_new := tbl; end; type TFSPrivateClose = procedure(data : pointer); procedure fd_free_entry(entry : PFileDescriptor); begin if entry = nil then exit; if not entry^.InUse then exit; { Free FS-level per-file metadata first } if (entry^.FSPrivate <> nil) and (entry^.FSCloseHook <> nil) then TFSPrivateClose(entry^.FSCloseHook)(entry^.FSPrivate); entry^.FSPrivate := nil; entry^.FSCloseHook := nil; if entry^.Directory <> nil then begin kfree(void(entry^.Directory)); entry^.Directory := nil; end; if entry^.FileName <> nil then begin kfree(void(entry^.FileName)); entry^.FileName := nil; end; if entry^.VFSDir <> nil then begin kfree(void(entry^.VFSDir)); entry^.VFSDir := nil; end; entry^.InUse := false; entry^.AsyncRefs := 0; entry^.DataSize := 0; entry^.DeviceOps := nil; entry^.DeviceData := nil; end; procedure fd_table_free(table : PFDTable); begin if table = nil then exit; fd_close_all(table); kfree(puint32(table)); end; function fd_alloc(table : PFDTable) : uint32; var i : uint32; begin fd_alloc := 0; if table = nil then exit; for i := 0 to MAX_FDS - 1 do begin if not table^.Entries[i].InUse then begin fd_alloc := i + 1; { 1-based } exit; end; end; end; function fd_get(table : PFDTable; handle : uint32) : PFileDescriptor; begin fd_get := nil; if table = nil then exit; if (handle = 0) or (handle > MAX_FDS) then exit; if not table^.Entries[handle - 1].InUse then exit; fd_get := @table^.Entries[handle - 1]; end; function fd_begin_async(entry : PFileDescriptor) : boolean; begin fd_begin_async := false; if entry = nil then exit; asm pushf; cli end; if entry^.InUse then begin entry^.AsyncRefs := entry^.AsyncRefs + 1; fd_begin_async := true; end; asm popf end; end; procedure fd_end_async(entry : PFileDescriptor); begin if entry = nil then exit; asm pushf; cli end; if entry^.AsyncRefs > 0 then entry^.AsyncRefs := entry^.AsyncRefs - 1; asm popf end; end; function fd_has_async(entry : PFileDescriptor) : boolean; begin fd_has_async := false; if entry = nil then exit; fd_has_async := entry^.AsyncRefs > 0; end; function fd_close(table : PFDTable; handle : uint32) : boolean; begin fd_close := false; if table = nil then exit; if (handle = 0) or (handle > MAX_FDS) then exit; if not table^.Entries[handle - 1].InUse then exit; fd_free_entry(@table^.Entries[handle - 1]); fd_close := true; end; procedure fd_close_all(table : PFDTable); var i : uint32; begin if table = nil then exit; for i := 0 to MAX_FDS - 1 do begin if table^.Entries[i].InUse then fd_free_entry(@table^.Entries[i]); end; end; procedure fd_close_for_volume(table : PFDTable; vol : PStorage_Volume); var i : uint32; begin if table = nil then exit; if vol = nil then exit; for i := 0 to MAX_FDS - 1 do begin if table^.Entries[i].InUse and (table^.Entries[i].Volume = vol) then fd_free_entry(@table^.Entries[i]); end; end; end.