Kernel - Implementing SysSpawn
authorJohn Hodge <[email protected]>
Mon, 30 Jan 2012 05:32:56 +0000 (13:32 +0800)
committerJohn Hodge <[email protected]>
Mon, 30 Jan 2012 05:32:56 +0000 (13:32 +0800)
- Working on proper VFS node reference counting
- Cleanups in binary.c related to changes

13 files changed:
Kernel/arch/armv7/proc.c
Kernel/arch/x86/include/mm_virt.h
Kernel/arch/x86/mm_virt.c
Kernel/arch/x86/proc.asm
Kernel/arch/x86/proc.c
Kernel/arch/x86_64/mm_virt.c
Kernel/arch/x86_64/proc.c
Kernel/binary.c
Kernel/include/acess.h
Kernel/include/hal_proc.h
Kernel/include/vfs_threads.h [new file with mode: 0644]
Kernel/syscalls.c
Kernel/vfs/handle.c

index c61590e..cd998f2 100644 (file)
@@ -62,11 +62,11 @@ tThread *Proc_GetCurThread(void)
        return gpCurrentThread;
 }
 
-void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataSize)
+void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, const char **ArgV, int DataSize)
 {
        Uint32  *usr_sp;
         int    i;
-       char    **envp;
+       const char      **envp;
        tVAddr  delta;
 
 //     Log_Debug("Proc", "Proc_StartUser: (Entrypoint=%p, Base=%p, ArgC=%i, ArgV=%p, DataSize=0x%x)",
index 31642c6..d84c965 100644 (file)
@@ -46,7 +46,7 @@
 // === FUNCTIONS ===
 extern void    MM_FinishVirtualInit(void);
 extern void    MM_SetCR3(Uint CR3);
-extern tPAddr  MM_Clone(void);
+extern tPAddr  MM_Clone(int bCloneUser);
 extern tVAddr  MM_NewKStack(void);
 extern tVAddr  MM_NewWorkerStack(Uint *InitialStack, size_t StackSize);
 
index d384142..bd7b1df 100644 (file)
@@ -574,7 +574,7 @@ void MM_ClearSpace(Uint32 CR3)
  * \fn tPAddr MM_Clone(void)
  * \brief Clone the current address space
  */
-tPAddr MM_Clone(void)
+tPAddr MM_Clone(int bNoUserCopy)
 {
        Uint    i, j;
        tPAddr  ret;
@@ -593,7 +593,7 @@ tPAddr MM_Clone(void)
        INVLPG( gaTmpDir );
        memsetd( gaTmpDir, 0, 1024 );
        
-       if( Threads_GetPID() != 0 )
+       if( Threads_GetPID() != 0 && !bNoUserCopy )
        {       
                // Copy Tables
                for( i = 0; i < 768; i ++)
index 3de4e0a..8da7971 100644 (file)
@@ -31,7 +31,9 @@ Proc_CloneInt:
        ; Save RSP
        mov eax, [esp+0x20+4]
        mov [eax], esp
+       push DWORD [esp+0x20+12]
        call MM_Clone
+       add esp, 4
        ; Save CR3
        mov esi, [esp+0x20+8]
        mov [esi], eax
index dd7027e..82a3f40 100644 (file)
@@ -45,7 +45,7 @@ extern void   APStartup(void);        // 16-bit AP startup code
 extern Uint    GetEIP(void);   // start.asm
 extern Uint    GetEIP_Sched(void);     // proc.asm
 extern void    NewTaskHeader(tThread *Thread, void *Fcn, int nArgs, ...);      // Actually takes cdecl args
-extern Uint    Proc_CloneInt(Uint *ESP, Uint32 *CR3);
+extern Uint    Proc_CloneInt(Uint *ESP, Uint32 *CR3, int bNoUserClone);
 extern Uint32  gaInitPageDir[1024];    // start.asm
 extern char    Kernel_Stack_Top[];
 extern int     giNumCPUs;
@@ -640,7 +640,7 @@ tPID Proc_Clone(Uint Flags)
        newThread->KernelStack = cur->KernelStack;
 
        // Clone state
-       eip = Proc_CloneInt(&newThread->SavedState.ESP, &newThread->Process->MemState.CR3);
+       eip = Proc_CloneInt(&newThread->SavedState.ESP, &newThread->Process->MemState.CR3, Flags & CLONE_NOUSER);
        if( eip == 0 ) {
                return 0;
        }
@@ -727,16 +727,16 @@ Uint Proc_MakeUserStack(void)
        return base + USER_STACK_SZ;
 }
 
-void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataSize)
+void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, const char **ArgV, int DataSize)
 {
        Uint    *stack;
         int    i;
-       char    **envp = NULL;
+       const char      **envp = NULL;
        Uint16  ss, cs;
        
        // Copy data to the user stack and free original buffer
        stack = (void*)Proc_MakeUserStack();
-       stack -= DataSize/sizeof(*stack);
+       stack -= (DataSize+sizeof(*stack)-1)/sizeof(*stack);
        memcpy( stack, ArgV, DataSize );
        free(ArgV);
        
@@ -744,7 +744,7 @@ void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataS
        if( DataSize )
        {
                Uint delta = (Uint)stack - (Uint)ArgV;
-               ArgV = (char**)stack;
+               ArgV = (const char**)stack;
                for( i = 0; ArgV[i]; i++ )      ArgV[i] += delta;
                envp = &ArgV[i+1];
                for( i = 0; envp[i]; i++ )      envp[i] += delta;
index 79513b9..89aeaa1 100644 (file)
@@ -924,6 +924,13 @@ tPAddr MM_Clone(void)
                        MM_RefPhys( TMPMAPLVL4(i) & PADDR_MASK );
                }
        }
+       else
+       {
+               for( i = 0; i < 256; i ++ )
+               {
+                       TMPMAPLVL4(i) = 0;
+               }
+       }
        
        // #4 Map in kernel pages
        for( i = 256; i < 512; i ++ )
index a8928ca..5f71a45 100644 (file)
@@ -586,10 +586,12 @@ Uint Proc_MakeUserStack(void)
        if(i != -1)     return 0;
        
        // Allocate Stack - Allocate incrementally to clean up MM_Dump output
+       // - Most of the user stack is the zero page
        for( i = 0; i < (USER_STACK_SZ-USER_STACK_PREALLOC)/0x1000; i++ )
        {
                MM_AllocateZero( base + (i<<12) );
        }
+       // - but the top USER_STACK_PREALLOC pages are actually allocated
        for( ; i < USER_STACK_SZ/0x1000; i++ )
        {
                tPAddr  alloc = MM_Allocate( base + (i<<12) );
@@ -606,11 +608,11 @@ Uint Proc_MakeUserStack(void)
        return base + USER_STACK_SZ;
 }
 
-void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataSize)
+void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, const char **ArgV, int DataSize)
 {
        Uint    *stack;
         int    i;
-       char    **envp = NULL;
+       const char      **envp = NULL;
        Uint16  ss, cs;
        
        
@@ -628,7 +630,7 @@ void Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataS
        if(DataSize)
        {
                Uint    delta = (Uint)stack - (Uint)ArgV;
-               ArgV = (char**)stack;
+               ArgV = (const char**)stack;
                for( i = 0; ArgV[i]; i++ )      ArgV[i] += delta;
                envp = &ArgV[i+1];
                for( i = 0; envp[i]; i++ )      envp[i] += delta;
index 20da5c4..a14bd4a 100644 (file)
@@ -7,6 +7,7 @@
 #include <binary.h>
 #include <mm_virt.h>
 #include <hal_proc.h>
+#include <vfs_threads.h>
 
 // === CONSTANTS ===
 #define BIN_LOWEST     MM_USER_MIN             // 1MiB
@@ -30,7 +31,7 @@ extern tKernelSymbol  gKernelSymbolsEnd[];
 extern tBinaryType     gELF_Info;
 
 // === PROTOTYPES ===
- int   Proc_Execve(const char *File, const char **ArgV, const char **EnvP);
+ int   Binary_int_CacheArgs(const char **Path, const char ***ArgV, const char ***EnvP, void *DestBuffer);
 tVAddr Binary_Load(const char *Path, tVAddr *EntryPoint);
 tBinary        *Binary_GetInfo(tMount MountID, tInode InodeID);
 tVAddr Binary_MapIn(tBinary *Binary, const char *Path, tVAddr LoadMin, tVAddr LoadMax);
@@ -78,18 +79,128 @@ int Proc_Spawn(const char *Path)
        
        LOG("stackPath = '%s'", stackPath);
        
-       if(Proc_Clone(CLONE_VM) == 0)
+       if(Proc_Clone(CLONE_VM|CLONE_NOUSER) == 0)
        {
                // CHILD
                const char      *args[2] = {stackPath, NULL};
                LOG("stackPath = '%s'", stackPath);
-               Proc_Execve(stackPath, args, &args[1]);
+               Proc_Execve(stackPath, args, &args[1], 0);
                for(;;);
        }
        LEAVE('i', 0);
        return 0;
 }
 
+/**
+ * \todo Document
+ */
+int Binary_int_CacheArgs(const char **Path, const char ***ArgV, const char ***EnvP, void *DestBuffer)
+{
+        int    size, argc=0, envc=0;
+        int    i;
+       char    *strbuf;
+       const char      **arrays;
+       
+       // Calculate size
+       size = 0;
+       if( ArgV && *ArgV )
+       {
+               const char      **argv = *ArgV;
+               for( argc = 0; argv[argc]; argc ++ )
+                       size += strlen( argv[argc] ) + 1;
+       }
+       if( EnvP && *EnvP )
+       {
+               const char      **envp = *EnvP;
+               for( envc = 0; envp[envc]; envc ++ )
+                       size += strlen( envp[envc] ) + 1;
+       }
+       size = (size + sizeof(void*)-1) & ~(sizeof(void*)-1);   // Word align
+       size += (argc+1+envc+1)*sizeof(void*);  // Arrays
+       if( Path )
+       {
+               size += strlen( *Path ) + 1;
+       }
+
+       if( DestBuffer )        
+       {
+               arrays = DestBuffer;
+               strbuf = (void*)&arrays[argc+1+envc+1];
+       
+               // Fill ArgV
+               if( ArgV && *ArgV )
+               {
+                       const char      **argv = *ArgV;
+                       for( i = 0; argv[i]; i ++ )
+                       {
+                               arrays[i] = strbuf;
+                               strcpy(strbuf, argv[i]);
+                               strbuf += strlen( argv[i] ) + 1;
+                       }
+                       *ArgV = arrays;
+                       arrays += i;
+               }
+               *arrays++ = NULL;
+               // Fill EnvP
+               if( EnvP && *EnvP )
+               {
+                       const char      **envp = *EnvP;
+                       for( i = 0; envp[i]; i ++ )
+                       {
+                               arrays[i] = strbuf;
+                               strcpy(strbuf, envp[i]);
+                               strbuf += strlen( envp[i] ) + 1;
+                       }
+                       *EnvP = arrays;
+                       arrays += i;
+               }
+               *arrays++ = NULL;
+               // Fill path
+               if( Path )
+               {
+                       strcpy(strbuf, *Path);
+                       *Path = strbuf;
+               }
+       }
+       
+       return size;
+}
+
+/**
+ * \brief Create a new process with the specified set of file descriptors
+ */
+int Proc_SysSpawn(const char *Binary, const char **ArgV, const char **EnvP, int nFD, int *FDs)
+{
+       void    *handles;
+       void    *cachebuf;
+        int    size;
+       tPID    ret;
+       
+       // --- Save File, ArgV and EnvP
+       size = Binary_int_CacheArgs( &Binary, &ArgV, &EnvP, NULL );
+       cachebuf = alloca( size );
+       Binary_int_CacheArgs( &Binary, &ArgV, &EnvP, cachebuf );
+
+       // Cache the VFS handles        
+       handles = VFS_SaveHandles(nFD, FDs);
+
+       // Create new process   
+       ret = Proc_Clone(CLONE_VM|CLONE_NOUSER);
+       if( ret == 0 )
+       {
+               VFS_RestoreHandles(nFD, handles);
+               VFS_FreeSavedHandles(nFD, handles);
+               Proc_Execve(Binary, ArgV, EnvP, size);
+               for(;;);
+       }
+       if( ret < 0 )
+       {
+               VFS_FreeSavedHandles(nFD, handles);
+       }
+       
+       return ret;
+}
+
 /**
  * \brief Replace the current user image with another
  * \param File File to load as the next image
@@ -97,61 +208,25 @@ int Proc_Spawn(const char *Path)
  * \param EnvP User's environment
  * \note Called Proc_ for historical reasons
  */
-int Proc_Execve(const char *File, const char **ArgV, const char **EnvP)
+int Proc_Execve(const char *File, const char **ArgV, const char **EnvP, int DataSize)
 {
-        int    argc, envc, i;
-        int    argenvBytes;
-       char    **argenvBuf, *strBuf;
-       char    **argvSaved, **envpSaved;
-       char    *savedFile;
+       void    *cachebuf;
        tVAddr  entry;
        Uint    base;   // Uint because Proc_StartUser wants it
+        int    argc;
        
        ENTER("sFile pArgV pEnvP", File, ArgV, EnvP);
        
-       // --- Save File, ArgV and EnvP (also get argc)
-       
-       // Count Arguments, Environment Variables and total string sizes
-       argenvBytes = 0;
-       for( argc = 0; ArgV && ArgV[argc]; argc++ )
-               argenvBytes += strlen(ArgV[argc])+1;
-       for( envc = 0; EnvP && EnvP[envc]; envc++ )
-               argenvBytes += strlen(EnvP[envc])+1;
-       LOG("argc = %i, envc = %i", envc);
-       argenvBytes = (argenvBytes + sizeof(void*)-1) & ~(sizeof(void*)-1);
-       argenvBytes += (argc+1)*sizeof(void*) + (envc+1)*sizeof(void*);
-       
-       // Allocate
-       argenvBuf = malloc(argenvBytes);
-       if(argenvBuf == NULL) {
-               Log_Error("Binary", "Proc_Execve - What the hell? The kernel is out of heap space");
-               LEAVE('i', 0);
-               return 0;
-       }
-       strBuf = (char*)argenvBuf + (argc+1)*sizeof(void*) + (envc+1)*sizeof(void*);
-       
-       // Populate
-       argvSaved = argenvBuf;
-       for( i = 0; i < argc; i++ )
-       {
-               argvSaved[i] = strBuf;
-               strcpy(argvSaved[i], ArgV[i]);
-               LOG("argv[%i] = '%s'", i, strBuf);
-               strBuf += strlen(ArgV[i])+1;
-       }
-       argvSaved[i] = NULL;
-       envpSaved = &argvSaved[i+1];
-       for( i = 0; i < envc; i++ )
+       // --- Save File, ArgV and EnvP
+       if( DataSize == 0 )
        {
-               envpSaved[i] = strBuf;
-               LOG("envp[%i] = '%s'", i, strBuf);
-               strcpy(envpSaved[i], EnvP[i]);
-               strBuf += strlen(EnvP[i])+1;
+               DataSize = Binary_int_CacheArgs( &File, &ArgV, &EnvP, NULL );
+               cachebuf = malloc( DataSize );
+               Binary_int_CacheArgs( &File, &ArgV, &EnvP, cachebuf );
        }
-       envpSaved[i] = NULL;
-       
-       savedFile = malloc(strlen(File)+1);
-       strcpy(savedFile, File);
+
+       // --- Get argc 
+       for( argc = 0; ArgV && ArgV[argc]; argc ++ );
        
        // --- Set Process Name
        Threads_SetName(File);
@@ -160,24 +235,19 @@ int Proc_Execve(const char *File, const char **ArgV, const char **EnvP)
        MM_ClearUser();
        
        // --- Load new binary
-       base = Binary_Load(savedFile, &entry);
-       free(savedFile);
+       base = Binary_Load(File, &entry);
        if(base == 0)
        {
-               free(argvSaved);
-               Log_Warning("Binary", "Proc_Execve - Unable to load '%s'", Threads_GetName(-1));
+               Log_Warning("Binary", "Proc_Execve - Unable to load '%s'", File);
                LEAVE('-');
                Threads_Exit(0, -10);
                for(;;);
        }
        
        LOG("entry = 0x%x, base = 0x%x", entry, base);
-
-//     MM_DumpTables(0, KERNEL_BASE);
-
        LEAVE('-');
        // --- And... Jump to it
-       Proc_StartUser(entry, base, argc, argvSaved, argenvBytes);
+       Proc_StartUser(entry, base, argc, ArgV, DataSize);
        for(;;);        // Tell GCC that we never return
 }
 
index df36060..de75804 100644 (file)
@@ -88,6 +88,8 @@ extern const char gsGitHash[];
  */
 //! Clone the entire process
 #define CLONE_VM       0x10
+//! Don't copy user pages
+#define CLONE_NOUSER   0x20
 /**
  * \}
  */
@@ -443,6 +445,10 @@ extern int CallWithArgArray(void *Function, int NArgs, Uint *Args);
 
 // --- Heap ---
 #include <heap.h>
+/**
+ * \brief Magic heap allocation function
+ */
+extern void    *alloca(size_t Size);
 
 // --- Modules ---
 /**
@@ -498,6 +504,8 @@ extern void Time_Delay(int Delay);
  */
 extern int     Proc_SpawnWorker(void (*Fcn)(void*), void *Data);
 extern int     Proc_Spawn(const char *Path);
+extern int     Proc_SysSpawn(const char *Binary, const char **ArgV, const char **EnvP, int nFD, int *FDs);
+extern int     Proc_Execve(const char *File, const char **ArgV, const char **EnvP, int DataSize);
 extern void    Threads_Exit(int TID, int Status);
 extern void    Threads_Yield(void);
 extern void    Threads_Sleep(void);
index 3a22e5e..0f21a1c 100644 (file)
@@ -58,7 +58,7 @@ extern tTID   Proc_NewKThread( void (*Fnc)(void*), void *Ptr );
  * \param DataSize     Size of the \a ArgV buffer in bytes
  * \note This function should free \a ArgV
  */
-extern void    Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, char **ArgV, int DataSize) NORETURN;
+extern void    Proc_StartUser(Uint Entrypoint, Uint Base, int ArgC, const char **ArgV, int DataSize) NORETURN;
 /**
  * \brief Call the fault handler for a thread
  * \param Thread       Thread that is at fault :)
diff --git a/Kernel/include/vfs_threads.h b/Kernel/include/vfs_threads.h
new file mode 100644 (file)
index 0000000..95ec227
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Acess2 Kernel
+ * - By John Hodge (thePowersGang)
+ *
+ * include/vfs_threads.h
+ * - Handle maintainance functions for the VFS used by threading code
+ */
+#ifndef _VFS_THREADS_H_
+#define _VFS_THREADS_H_
+
+// === FUNCTIONS ===
+extern void    VFS_ReferenceUserHandles(void);
+extern void    VFS_CloseAllUserHandles(void);
+
+extern void    *VFS_SaveHandles(int NumFDs, int *FDs);
+extern void    VFS_RestoreHandles(int NumFDs, void *Handles);
+extern void    VFS_FreeSavedHandles(int NumFDs, void *Handles);
+
+#endif
index 4e4527e..e62ab06 100644 (file)
@@ -22,7 +22,6 @@
        if(!(v)||!Syscall_ValidString((v))){ret=-1;err=-EINVAL;break;}
 
 // === IMPORTS ===
-extern int     Proc_Execve(char *File, char **ArgV, char **EnvP);
 extern Uint    Binary_Load(const char *file, Uint *entryPoint);
 
 // === PROTOTYPES ===
@@ -178,8 +177,8 @@ void SyscallHandler(tSyscallRegs *Regs)
                        }
                }
                LEAVE('s', "Assuming 0");
-               // Path, **Argv, **Envp
-               ret = Proc_Execve( (char*)Regs->Arg1, (char**)Regs->Arg2, (char**)Regs->Arg3 );
+               // Path, **Argv, **Envp, DataSize (=0 to tell it to create a copy)
+               ret = Proc_Execve( (const char*)Regs->Arg1, (const char**)Regs->Arg2, (const char**)Regs->Arg3, 0 );
                break;
        // -- Load a binary into the current process
        case SYS_LOADBIN:
index 42a8fc4..ae8b058 100644 (file)
@@ -9,6 +9,7 @@
 #include "vfs_int.h"
 #include "vfs_ext.h"
 #include <threads.h>   // GetMaxFD
+#include <vfs_threads.h>       // Handle maintainance
 
 // === CONSTANTS ===
 #define MAX_KERNEL_FILES       128
@@ -113,3 +114,127 @@ int VFS_AllocHandle(int bIsUser, tVFS_Node *Node, int Mode)
        
        return -1;
 }
+
+void VFS_ReferenceUserHandles(void)
+{
+        int    i;
+        int    max_handles = *Threads_GetMaxFD();
+       
+       for( i = 0; i < max_handles; i ++ )
+       {
+               tVFS_Handle     *h;
+               h = &gaUserHandles[i];
+               if( h->Node->Type && h->Node->Type->Reference )
+                       h->Node->Type->Reference( h->Node );
+       }
+}
+
+void VFS_CloseAllUserHandles(void)
+{
+        int    i;
+        int    max_handles = *Threads_GetMaxFD();
+       
+       for( i = 0; i < max_handles; i ++ )
+       {
+               tVFS_Handle     *h;
+               h = &gaUserHandles[i];
+               if( h->Node->Type && h->Node->Type->Close )
+                       h->Node->Type->Close( h->Node );
+       }
+}
+
+/**
+ * \brief Take a backup of a set of file descriptors
+ */
+void *VFS_SaveHandles(int NumFDs, int *FDs)
+{
+       tVFS_Handle     *ret;
+        int    i;
+       
+       // Check if this process has any handles
+       if( MM_GetPhysAddr( (tVAddr)gaUserHandles ) == 0 )
+               return NULL;
+
+       // Allocate
+       ret = malloc( NumFDs * sizeof(tVFS_Handle) );
+       if( !ret )
+               return NULL;    
+
+       // Take copies of the handles
+       for( i = 0; i < NumFDs; i ++ )
+       {
+               tVFS_Handle     *h;
+               h = VFS_GetHandle(FDs[i] & (VFS_KERNEL_FLAG - 1));
+               if(!h) {
+                       Log_Warning("VFS", "VFS_SaveHandles - Invalid FD %i",
+                               FDs[i] & (VFS_KERNEL_FLAG - 1) );
+                       free(ret);
+                       return NULL;
+               }
+               memcpy( &ret[i], h, sizeof(tVFS_Handle) );
+               
+               // Reference node
+               if( h->Node->Type && h->Node->Type->Reference )
+                       h->Node->Type->Reference( h->Node );
+       }       
+
+       return ret;
+}
+
+void VFS_RestoreHandles(int NumFDs, void *Handles)
+{
+       tVFS_Handle     *handles = Handles;
+        int    i;
+
+       // NULL = nothing to do
+       if( !Handles )
+               return ;        
+
+       // Check if there is already a set of handles
+       if( MM_GetPhysAddr( (tVAddr)gaUserHandles ) != 0 )
+               return ;
+       
+       
+       // Allocate user handle area
+       {
+               Uint    addr, size;
+                int    max_handles = *Threads_GetMaxFD();
+               size = max_handles * sizeof(tVFS_Handle);
+               for(addr = 0; addr < size; addr += 0x1000)
+               {
+                       if( !MM_Allocate( (tVAddr)gaUserHandles + addr ) )
+                       {
+                               Warning("OOM - VFS_AllocHandle");
+                               Threads_Exit(0, 0xFF);  // Terminate user
+                       }
+               }
+               memset( gaUserHandles, 0, size );
+       }
+       
+       // Restore handles
+       memcpy( gaUserHandles, handles, NumFDs * sizeof(tVFS_Handle) );
+       // Reference when copied
+       for( i = 0; i < NumFDs; i ++ )
+       {
+               tVFS_Handle     *h = &handles[i];
+       
+               if( h->Node->Type && h->Node->Type->Reference )
+                       h->Node->Type->Reference( h->Node );
+       }
+}
+
+void VFS_FreeSavedHandles(int NumFDs, void *Handles)
+{
+       tVFS_Handle     *handles = Handles;
+        int    i;
+       
+       // Dereference all saved nodes
+       for( i = 0; i < NumFDs; i ++ )
+       {
+               tVFS_Handle     *h = &handles[i];
+       
+               if( h->Node->Type && h->Node->Type->Close )
+                       h->Node->Type->Close( h->Node );
+       }
+       free( Handles );
+}

UCC git Repository :: git.ucc.asn.au