mirror of
https://github.com/reactos/reactos.git
synced 2025-02-24 09:25:10 +00:00
Fixed compilation bug
Made a start on some documentation in info format svn path=/trunk/; revision=985
This commit is contained in:
parent
b49921cb96
commit
3cd2fc22d7
12 changed files with 386 additions and 365 deletions
|
@ -41,7 +41,7 @@ FS_DRIVERS = vfat
|
|||
# FS_DRIVERS = minix ext2 template
|
||||
KERNEL_SERVICES = $(DEVICE_DRIVERS) $(FS_DRIVERS)
|
||||
|
||||
APPS = args hello shell test cat bench apc shm lpc thread event
|
||||
APPS = args hello shell test cat bench apc shm lpc thread event file
|
||||
|
||||
all: buildno $(COMPONENTS) $(DLLS) $(SUBSYS) $(LOADERS) $(KERNEL_SERVICES) $(APPS)
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ else
|
|||
endif
|
||||
|
||||
file.exe: $(OBJECTS)
|
||||
$(CPP) $(OBJECTS) $(BASE_CFLAGS) -o file.exe
|
||||
$(CC) $(OBJECTS) $(BASE_CFLAGS) -o file.exe
|
||||
$(NM) --numeric-sort file.exe > file.sym
|
||||
|
||||
include ../../rules.mak
|
||||
|
|
50
reactos/apps/tests/file/file.c
Normal file
50
reactos/apps/tests/file/file.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
/***********************************************************
|
||||
* File read/write test utility *
|
||||
**********************************************************/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
HANDLE file;
|
||||
char buffer[4096];
|
||||
DWORD wrote;
|
||||
int c;
|
||||
|
||||
file = CreateFile("test.dat",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_ALWAYS,
|
||||
0,
|
||||
0);
|
||||
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
printf("Error opening file (Status %x)\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Writing file\n");
|
||||
for (c = 0; c < 1024; c++)
|
||||
{
|
||||
if (WriteFile( file, buffer, 4096, &wrote, NULL) == FALSE)
|
||||
{
|
||||
printf("Error writing file (Status %x)\n", GetLastError());
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Reading file\n");
|
||||
for (c = 0; c < 1024; c++)
|
||||
{
|
||||
if (ReadFile( file, buffer, 4096, &wrote, NULL) == FALSE)
|
||||
{
|
||||
printf("Error reading file (Status %x)\n", GetLastError());
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Finished\n");
|
||||
}
|
100
reactos/doc/psmgr.texi
Normal file
100
reactos/doc/psmgr.texi
Normal file
|
@ -0,0 +1,100 @@
|
|||
\input texinfo @c -*-texinfo-*-
|
||||
@c %**start of header
|
||||
@setfilename psmgr.info
|
||||
@settitle Process Manager Design and Implementation
|
||||
@setchapternewpage odd
|
||||
@c %**end of header
|
||||
|
||||
@ifinfo
|
||||
This document describes the ReactOS process manager design and implementation
|
||||
@end ifinfo
|
||||
|
||||
@titlepage
|
||||
@title Process Manager Design and Implementation
|
||||
@author David Welch <david.welch@seh.ox.ac.uk>
|
||||
@page
|
||||
@vskip 0pt plus 1filll
|
||||
Copyright @copyright{} 1999 David Welch
|
||||
@end titlepage
|
||||
|
||||
@node Top, Overview, , (dir)
|
||||
@comment node-name, next, previous, up
|
||||
|
||||
@ifinfo
|
||||
This document describes the design and implementation of the reactos process
|
||||
manager
|
||||
@end ifinfo
|
||||
|
||||
@menu
|
||||
* Overview:: Overview of this document
|
||||
* External Interfaces:: External Interfaces
|
||||
* Data Structures:: Data Structures
|
||||
* Concept Index:: This index has two entries
|
||||
@end menu
|
||||
|
||||
@node Overview, External Interfaces, top, top
|
||||
@comment node-name, next, previous, up
|
||||
@chapter Overview
|
||||
|
||||
This document describes the design and implementation of the ReactOS process
|
||||
manager. ReactOS is a GPLed operating system that attempts to be compatible
|
||||
with Windows NT, for more information see @uref{http://www.reactos.com}.
|
||||
|
||||
@node External Interfaces, Data Structures, Overview, top
|
||||
@comment node-name, next, previous, up
|
||||
@chapter External Interfaces
|
||||
|
||||
This chapter describes the external interfaces provided by the process
|
||||
manager both to user-mode and to the rest of the kernel.
|
||||
|
||||
@menu
|
||||
* NtCreateProcess:: Creates a new process
|
||||
@end menu
|
||||
|
||||
@node NtCreateProcess, , , External Interfaces
|
||||
@comment node-name, next, previous, up
|
||||
|
||||
@deftypefn Function NTSTATUS NtCreateProcess
|
||||
|
||||
The parameters are
|
||||
@itemize @bullet
|
||||
@item
|
||||
HANDLE ProcessHandle: Received a handle for the created process on return.
|
||||
@item
|
||||
ACCESS_MASK DesiredAccess: Specifies the requested types of access to
|
||||
the created process.
|
||||
@item
|
||||
POBJECT_ATTRIBUTES ObjectAttributes: Specifies various attributes for the
|
||||
created process.
|
||||
@item
|
||||
HANDLE ParentProcessHandle: Specifies the parent process for the created
|
||||
process.
|
||||
@item
|
||||
BOOLEAN InheritObjectTable: True if the new process such inherit its parent's
|
||||
handles.
|
||||
@item
|
||||
HANDLE SectionHandle: If this parameter is NULL then the new process's
|
||||
address space will be a copy of its parent's. Otherwise the new process's
|
||||
address space will contain a mapping of the section pointed to by this
|
||||
handle and NTDLL.
|
||||
@item
|
||||
HANDLE DebugPort: Specifies a handle that will receive debug messages
|
||||
associated with this process.
|
||||
@item
|
||||
HANDLE ExceptionPort: Specifies a handle that will receive exception messages
|
||||
associated with this process.
|
||||
@end itemize
|
||||
|
||||
The return value is either STATUS_SUCCESS or a value indicating the reason
|
||||
by failure.
|
||||
@end deftypefn
|
||||
|
||||
@node Data Structures, Concept Index, External Interfaces, top
|
||||
@comment node-name, next, previous, up
|
||||
@chapter Data Structures
|
||||
|
||||
@node Concept Index, ,Data Structures, Top
|
||||
@unnumbered Concept Index
|
||||
@printindex cp
|
||||
@contents
|
||||
@bye
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* $Id: fat.c,v 1.1 1999/12/11 21:14:48 dwelch Exp $
|
||||
* $Id: fat.c,v 1.2 2000/02/14 14:13:34 dwelch Exp $
|
||||
*
|
||||
* COPYRIGHT: See COPYING in the top level directory
|
||||
* PROJECT: ReactOS kernel
|
||||
|
@ -337,9 +337,11 @@ void FAT16WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite,
|
|||
* FUNCTION: Writes a cluster to the FAT16 physical and in-memory tables
|
||||
*/
|
||||
{
|
||||
ULONG FATsector;
|
||||
PUSHORT Block;
|
||||
DbgPrint("FAT16WriteCluster %u : %u\n",ClusterToWrite,NewValue);
|
||||
ULONG FATsector;
|
||||
PUSHORT Block;
|
||||
|
||||
DbgPrint("FAT16WriteCluster %u : %u\n", ClusterToWrite, NewValue);
|
||||
|
||||
Block=(PUSHORT)DeviceExt->FAT;
|
||||
FATsector=ClusterToWrite/(512/sizeof(USHORT));
|
||||
|
||||
|
@ -384,12 +386,18 @@ void WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite,
|
|||
* FUNCTION: Write a changed FAT entry
|
||||
*/
|
||||
{
|
||||
if(DeviceExt->FatType==FAT16)
|
||||
FAT16WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
else if(DeviceExt->FatType==FAT32)
|
||||
FAT32WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
if (DeviceExt->FatType==FAT16)
|
||||
{
|
||||
FAT16WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
}
|
||||
else if (DeviceExt->FatType==FAT32)
|
||||
{
|
||||
FAT32WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
}
|
||||
else
|
||||
FAT12WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
{
|
||||
FAT12WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
ULONG GetNextWriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
||||
|
@ -397,29 +405,41 @@ ULONG GetNextWriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|||
* FUNCTION: Determines the next cluster to be written
|
||||
*/
|
||||
{
|
||||
ULONG LastCluster, NewCluster;
|
||||
DPRINT("GetNextWriteCluster(DeviceExt %x, CurrentCluster %x)\n",
|
||||
DeviceExt,CurrentCluster);
|
||||
ULONG LastCluster, NewCluster;
|
||||
|
||||
DPRINT1("GetNextWriteCluster(DeviceExt %x, CurrentCluster %x)\n",
|
||||
DeviceExt,CurrentCluster);
|
||||
|
||||
/* Find out what was happening in the last cluster's AU */
|
||||
LastCluster=GetNextCluster(DeviceExt,CurrentCluster);
|
||||
LastCluster=GetNextCluster(DeviceExt,
|
||||
CurrentCluster);
|
||||
/* Check to see if we must append or overwrite */
|
||||
if (LastCluster==0xffffffff)
|
||||
{//we are after last existing cluster : we must add one to file
|
||||
if (LastCluster == 0xffffffff)
|
||||
{
|
||||
/* we are after last existing cluster : we must add one to file */
|
||||
/* Append */
|
||||
/* Firstly, find the next available open allocation unit */
|
||||
if(DeviceExt->FatType == FAT16)
|
||||
NewCluster = FAT16FindAvailableCluster(DeviceExt);
|
||||
else if(DeviceExt->FatType == FAT32)
|
||||
NewCluster = FAT32FindAvailableCluster(DeviceExt);
|
||||
if (DeviceExt->FatType == FAT16)
|
||||
{
|
||||
NewCluster = FAT16FindAvailableCluster(DeviceExt);
|
||||
DPRINT1("NewCluster %x\n", NewCluster);
|
||||
}
|
||||
else if (DeviceExt->FatType == FAT32)
|
||||
{
|
||||
NewCluster = FAT32FindAvailableCluster(DeviceExt);
|
||||
}
|
||||
else
|
||||
NewCluster = FAT12FindAvailableCluster(DeviceExt);
|
||||
{
|
||||
NewCluster = FAT12FindAvailableCluster(DeviceExt);
|
||||
}
|
||||
/* Mark the new AU as the EOF */
|
||||
WriteCluster(DeviceExt, NewCluster, 0xFFFFFFFF);
|
||||
/* Now, write the AU of the LastCluster with the value of the newly
|
||||
found AU */
|
||||
if(CurrentCluster)
|
||||
WriteCluster(DeviceExt, CurrentCluster, NewCluster);
|
||||
if(CurrentCluster)
|
||||
{
|
||||
WriteCluster(DeviceExt, CurrentCluster, NewCluster);
|
||||
}
|
||||
/* Return NewCluster as CurrentCluster */
|
||||
return NewCluster;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $Id: rw.c,v 1.1 1999/12/11 21:14:49 dwelch Exp $
|
||||
/* $Id: rw.c,v 1.2 2000/02/14 14:13:34 dwelch Exp $
|
||||
*
|
||||
* COPYRIGHT: See COPYING in the top level directory
|
||||
* PROJECT: ReactOS kernel
|
||||
|
@ -167,7 +167,10 @@ NTSTATUS FsdWriteFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,
|
|||
PVFATCCB pCcb;
|
||||
PVOID Temp;
|
||||
ULONG TempLength,Length2=Length;
|
||||
|
||||
|
||||
DPRINT1("FsdWriteFile(FileObject %x, Buffer %x, Length %x, "
|
||||
"WriteOffset %x\n", FileObject, Buffer, Length, WriteOffset);
|
||||
|
||||
/* Locate the first cluster of the file */
|
||||
assert(FileObject);
|
||||
pCcb=(PVFATCCB)(FileObject->FsContext2);
|
||||
|
@ -175,144 +178,184 @@ NTSTATUS FsdWriteFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,
|
|||
Fcb = pCcb->pFcb;
|
||||
assert(Fcb);
|
||||
if (DeviceExt->FatType == FAT32)
|
||||
CurrentCluster = Fcb->entry.FirstCluster+Fcb->entry.FirstClusterHigh*65536;
|
||||
{
|
||||
CurrentCluster =
|
||||
Fcb->entry.FirstCluster+Fcb->entry.FirstClusterHigh*65536;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentCluster = Fcb->entry.FirstCluster;
|
||||
}
|
||||
FirstCluster=CurrentCluster;
|
||||
|
||||
/* Allocate a buffer to hold 1 cluster of data */
|
||||
|
||||
Temp = ExAllocatePool(NonPagedPool,DeviceExt->BytesPerCluster);
|
||||
Temp = ExAllocatePool(NonPagedPool, DeviceExt->BytesPerCluster);
|
||||
assert(Temp);
|
||||
|
||||
/* Find the cluster according to the offset in the file */
|
||||
|
||||
if (CurrentCluster==1)
|
||||
{ //root of FAT16 or FAT12
|
||||
CurrentCluster=DeviceExt->rootStart+WriteOffset
|
||||
/DeviceExt->BytesPerCluster*DeviceExt->Boot->SectorsPerCluster;
|
||||
}
|
||||
else
|
||||
if (CurrentCluster==0)
|
||||
{// file of size 0 : allocate first cluster
|
||||
CurrentCluster=GetNextWriteCluster(DeviceExt,0);
|
||||
if (DeviceExt->FatType == FAT32)
|
||||
{
|
||||
Fcb->entry.FirstClusterHigh=CurrentCluster>>16;
|
||||
Fcb->entry.FirstCluster=CurrentCluster;
|
||||
{
|
||||
CurrentCluster=DeviceExt->rootStart+WriteOffset
|
||||
/ DeviceExt->BytesPerCluster*DeviceExt->Boot->SectorsPerCluster;
|
||||
}
|
||||
else
|
||||
Fcb->entry.FirstCluster=CurrentCluster;
|
||||
}
|
||||
else
|
||||
for (FileOffset=0; FileOffset < WriteOffset / DeviceExt->BytesPerCluster; FileOffset++)
|
||||
{
|
||||
CurrentCluster = GetNextCluster(DeviceExt,CurrentCluster);
|
||||
if (CurrentCluster==0)
|
||||
{
|
||||
/*
|
||||
* File of size zero
|
||||
*/
|
||||
CurrentCluster=GetNextWriteCluster(DeviceExt,0);
|
||||
if (DeviceExt->FatType == FAT32)
|
||||
{
|
||||
Fcb->entry.FirstClusterHigh = CurrentCluster>>16;
|
||||
Fcb->entry.FirstCluster = CurrentCluster;
|
||||
}
|
||||
else
|
||||
Fcb->entry.FirstCluster=CurrentCluster;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (FileOffset=0;
|
||||
FileOffset < WriteOffset / DeviceExt->BytesPerCluster;
|
||||
FileOffset++)
|
||||
{
|
||||
CurrentCluster = GetNextCluster(DeviceExt,CurrentCluster);
|
||||
}
|
||||
}
|
||||
CHECKPOINT;
|
||||
}
|
||||
CHECKPOINT;
|
||||
|
||||
|
||||
/*
|
||||
If the offset in the cluster doesn't fall on the cluster boundary then
|
||||
we have to write only from the specified offset
|
||||
*/
|
||||
|
||||
* If the offset in the cluster doesn't fall on the cluster boundary
|
||||
* then we have to write only from the specified offset
|
||||
*/
|
||||
|
||||
if ((WriteOffset % DeviceExt->BytesPerCluster)!=0)
|
||||
{
|
||||
CHECKPOINT;
|
||||
TempLength = min(Length,DeviceExt->BytesPerCluster -
|
||||
(WriteOffset % DeviceExt->BytesPerCluster));
|
||||
/* Read in the existing cluster data */
|
||||
if (FirstCluster==1)
|
||||
VFATReadSectors(DeviceExt->StorageDevice,CurrentCluster
|
||||
,DeviceExt->Boot->SectorsPerCluster,Temp);
|
||||
else
|
||||
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
||||
{
|
||||
CHECKPOINT;
|
||||
TempLength = min(Length,DeviceExt->BytesPerCluster -
|
||||
(WriteOffset % DeviceExt->BytesPerCluster));
|
||||
/* Read in the existing cluster data */
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATReadSectors(DeviceExt->StorageDevice,
|
||||
CurrentCluster,
|
||||
DeviceExt->Boot->SectorsPerCluster,
|
||||
Temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
||||
}
|
||||
|
||||
/* Overwrite the last parts of the data as necessary */
|
||||
memcpy(Temp + (WriteOffset % DeviceExt->BytesPerCluster), Buffer,
|
||||
/* Overwrite the last parts of the data as necessary */
|
||||
memcpy(Temp + (WriteOffset % DeviceExt->BytesPerCluster),
|
||||
Buffer,
|
||||
TempLength);
|
||||
|
||||
/* Write the cluster back */
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,CurrentCluster
|
||||
,DeviceExt->Boot->SectorsPerCluster,Temp);
|
||||
CurrentCluster += DeviceExt->Boot->SectorsPerCluster;
|
||||
|
||||
/* Write the cluster back */
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,
|
||||
CurrentCluster,
|
||||
DeviceExt->Boot->SectorsPerCluster,
|
||||
Temp);
|
||||
CurrentCluster += DeviceExt->Boot->SectorsPerCluster;
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATWriteCluster(DeviceExt,Temp,CurrentCluster);
|
||||
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
||||
}
|
||||
Length2 -= TempLength;
|
||||
Buffer = Buffer + TempLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATWriteCluster(DeviceExt,Temp,CurrentCluster);
|
||||
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
||||
}
|
||||
Length2 -= TempLength;
|
||||
Buffer = Buffer + TempLength;
|
||||
}
|
||||
CHECKPOINT;
|
||||
|
||||
|
||||
/* Write the buffer in chunks of 1 cluster */
|
||||
|
||||
|
||||
while (Length2 >= DeviceExt->BytesPerCluster)
|
||||
{
|
||||
{
|
||||
CHECKPOINT;
|
||||
if (CurrentCluster == 0)
|
||||
{
|
||||
ExFreePool(Temp);
|
||||
return(STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,
|
||||
CurrentCluster,
|
||||
DeviceExt->Boot->SectorsPerCluster,
|
||||
Buffer);
|
||||
CurrentCluster += DeviceExt->Boot->SectorsPerCluster;
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATWriteCluster(DeviceExt,Buffer,CurrentCluster);
|
||||
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
||||
}
|
||||
Buffer = Buffer + DeviceExt->BytesPerCluster;
|
||||
Length2 -= DeviceExt->BytesPerCluster;
|
||||
}
|
||||
CHECKPOINT;
|
||||
if (CurrentCluster == 0)
|
||||
{
|
||||
ExFreePool(Temp);
|
||||
return(STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,CurrentCluster
|
||||
,DeviceExt->Boot->SectorsPerCluster,Buffer);
|
||||
CurrentCluster += DeviceExt->Boot->SectorsPerCluster;
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATWriteCluster(DeviceExt,Buffer,CurrentCluster);
|
||||
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
||||
}
|
||||
Buffer = Buffer + DeviceExt->BytesPerCluster;
|
||||
Length2 -= DeviceExt->BytesPerCluster;
|
||||
}
|
||||
CHECKPOINT;
|
||||
|
||||
|
||||
/* Write the remainder */
|
||||
|
||||
|
||||
if (Length2 > 0)
|
||||
{
|
||||
CHECKPOINT;
|
||||
if (CurrentCluster == 0)
|
||||
{
|
||||
ExFreePool(Temp);
|
||||
return(STATUS_UNSUCCESSFUL);
|
||||
CHECKPOINT;
|
||||
if (CurrentCluster == 0)
|
||||
{
|
||||
ExFreePool(Temp);
|
||||
return(STATUS_UNSUCCESSFUL);
|
||||
}
|
||||
CHECKPOINT;
|
||||
/* Read in the existing cluster data */
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATReadSectors(DeviceExt->StorageDevice,
|
||||
CurrentCluster,
|
||||
DeviceExt->Boot->SectorsPerCluster,
|
||||
Temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
||||
CHECKPOINT;
|
||||
memcpy(Temp, Buffer, Length2);
|
||||
CHECKPOINT;
|
||||
if (FirstCluster==1)
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,
|
||||
CurrentCluster,
|
||||
DeviceExt->Boot->SectorsPerCluster,
|
||||
Temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
VFATWriteCluster(DeviceExt,Temp,CurrentCluster);
|
||||
}
|
||||
}
|
||||
CHECKPOINT;
|
||||
}
|
||||
CHECKPOINT;
|
||||
/* Read in the existing cluster data */
|
||||
if (FirstCluster==1)
|
||||
VFATReadSectors(DeviceExt->StorageDevice,CurrentCluster
|
||||
,DeviceExt->Boot->SectorsPerCluster,Temp);
|
||||
else
|
||||
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
||||
CHECKPOINT;
|
||||
memcpy(Temp, Buffer, Length2);
|
||||
CHECKPOINT;
|
||||
if (FirstCluster==1)
|
||||
|
||||
/*
|
||||
* FIXME : set last write time and date
|
||||
*/
|
||||
if (Fcb->entry.FileSize < WriteOffset+Length
|
||||
&& !(Fcb->entry.Attrib & FILE_ATTRIBUTE_DIRECTORY))
|
||||
{
|
||||
VFATWriteSectors(DeviceExt->StorageDevice,CurrentCluster
|
||||
,DeviceExt->Boot->SectorsPerCluster,Temp);
|
||||
Fcb->entry.FileSize = WriteOffset+Length;
|
||||
/*
|
||||
* update entry in directory
|
||||
*/
|
||||
updEntry(DeviceExt,FileObject);
|
||||
}
|
||||
else
|
||||
VFATWriteCluster(DeviceExt,Temp,CurrentCluster);
|
||||
}
|
||||
CHECKPOINT;
|
||||
//FIXME : set last write time and date
|
||||
if(Fcb->entry.FileSize<WriteOffset+Length
|
||||
&& !(Fcb->entry.Attrib &FILE_ATTRIBUTE_DIRECTORY))
|
||||
{
|
||||
Fcb->entry.FileSize=WriteOffset+Length;
|
||||
// update entry in directory
|
||||
updEntry(DeviceExt,FileObject);
|
||||
}
|
||||
|
||||
ExFreePool(Temp);
|
||||
return(STATUS_SUCCESS);
|
||||
return (STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
NTSTATUS FsdWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
||||
|
|
|
@ -30,3 +30,4 @@ cp apps/lpc/lpcsrv.exe $1/reactos/bin
|
|||
cp apps/lpc/lpcclt.exe $1/reactos/bin
|
||||
cp apps/thread/thread.exe $1/reactos/bin
|
||||
cp apps/event/event.exe $1/reactos/bin
|
||||
cp apps/file/file.exe $1/reactos/bin
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $Id: ppb.c,v 1.1 2000/02/13 16:05:16 dwelch Exp $
|
||||
/* $Id: ppb.c,v 1.2 2000/02/14 14:13:33 dwelch Exp $
|
||||
*
|
||||
* COPYRIGHT: See COPYING in the top level directory
|
||||
* PROJECT: ReactOS system libraries
|
||||
|
@ -120,25 +120,22 @@ RtlCreateProcessParameters (
|
|||
|
||||
/* copy current directory */
|
||||
Dest = (PWCHAR)(((PBYTE)Param) +
|
||||
sizeof(RTL_USER_PROCESS_PARAMETERS) +
|
||||
(256 * sizeof(WCHAR)));
|
||||
sizeof(RTL_USER_PROCESS_PARAMETERS));
|
||||
|
||||
Param->CurrentDirectory.DosPath.Buffer = Dest;
|
||||
Param->CurrentDirectory.DosPath.MaximumLength = MAX_PATH * sizeof(WCHAR);
|
||||
if (CurrentDirectory != NULL)
|
||||
{
|
||||
Param->CurrentDirectory.DosPath.Length = CurrentDirectory->Length;
|
||||
Param->CurrentDirectory.DosPath.MaximumLength =
|
||||
CurrentDirectory->Length + sizeof(WCHAR);
|
||||
memcpy(Dest,
|
||||
CurrentDirectory->Buffer,
|
||||
CurrentDirectory->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + CurrentDirectory->Length);
|
||||
}
|
||||
*Dest = 0;
|
||||
|
||||
Dest = (PWCHAR)(((PBYTE)Param) + sizeof(RTL_USER_PROCESS_PARAMETERS) +
|
||||
(256 * sizeof(WCHAR)) + (MAX_PATH * sizeof(WCHAR)));
|
||||
|
||||
/* (256 * sizeof(WCHAR)) + */ (MAX_PATH * sizeof(WCHAR)));
|
||||
|
||||
/* copy library path */
|
||||
Param->LibraryPath.Buffer = Dest;
|
||||
if (LibraryPath != NULL)
|
||||
|
@ -214,7 +211,6 @@ RtlCreateProcessParameters (
|
|||
*Ppb = Param;
|
||||
RtlReleasePebLock ();
|
||||
|
||||
return(Status);
|
||||
}
|
||||
|
||||
VOID STDCALL RtlDestroyProcessParameters (PRTL_USER_PROCESS_PARAMETERS Ppb)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $Id: process.c,v 1.12 2000/02/13 16:05:16 dwelch Exp $
|
||||
/* $Id: process.c,v 1.13 2000/02/14 14:13:33 dwelch Exp $
|
||||
*
|
||||
* COPYRIGHT: See COPYING in the top level directory
|
||||
* PROJECT: ReactOS system libraries
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include <ntdll/base.h>
|
||||
#include <ntdll/rtl.h>
|
||||
|
||||
//#define NDEBUG
|
||||
#define NDEBUG
|
||||
#include <ntdll/ntdll.h>
|
||||
|
||||
/* FUNCTIONS ****************************************************************/
|
||||
|
@ -276,191 +276,4 @@ NTSTATUS STDCALL RtlCreateUserProcess(PUNICODE_STRING CommandLine,
|
|||
return(STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
VOID STDCALL RtlAcquirePebLock(VOID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
VOID STDCALL RtlReleasePebLock(VOID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
NTSTATUS
|
||||
STDCALL
|
||||
RtlCreateProcessParameters (
|
||||
PRTL_USER_PROCESS_PARAMETERS *Ppb,
|
||||
PUNICODE_STRING CommandLine,
|
||||
PUNICODE_STRING LibraryPath,
|
||||
PUNICODE_STRING CurrentDirectory,
|
||||
PUNICODE_STRING ImageName,
|
||||
PVOID Environment,
|
||||
PUNICODE_STRING Title,
|
||||
PUNICODE_STRING Desktop,
|
||||
PUNICODE_STRING Reserved,
|
||||
PVOID Reserved2
|
||||
)
|
||||
{
|
||||
NTSTATUS Status = STATUS_SUCCESS;
|
||||
PRTL_USER_PROCESS_PARAMETERS Param = NULL;
|
||||
ULONG RegionSize = 0;
|
||||
ULONG DataSize = 0;
|
||||
PWCHAR Dest;
|
||||
|
||||
DPRINT ("RtlCreateProcessParameters\n");
|
||||
|
||||
RtlAcquirePebLock ();
|
||||
|
||||
/* size of process parameter block */
|
||||
DataSize = sizeof (RTL_USER_PROCESS_PARAMETERS);
|
||||
|
||||
/* size of (reserved) buffer */
|
||||
DataSize += (256 * sizeof(WCHAR));
|
||||
|
||||
/* size of current directory buffer */
|
||||
DataSize += (MAX_PATH * sizeof(WCHAR));
|
||||
|
||||
/* add string lengths */
|
||||
if (LibraryPath != NULL)
|
||||
DataSize += (LibraryPath->Length + sizeof(WCHAR));
|
||||
|
||||
if (CommandLine != NULL)
|
||||
DataSize += (CommandLine->Length + sizeof(WCHAR));
|
||||
|
||||
if (ImageName != NULL)
|
||||
DataSize += (ImageName->Length + sizeof(WCHAR));
|
||||
|
||||
if (Title != NULL)
|
||||
DataSize += (Title->Length + sizeof(WCHAR));
|
||||
|
||||
if (Desktop != NULL)
|
||||
DataSize += (Desktop->Length + sizeof(WCHAR));
|
||||
|
||||
if (Reserved != NULL)
|
||||
DataSize += (Reserved->Length + sizeof(WCHAR));
|
||||
|
||||
/* Calculate the required block size */
|
||||
RegionSize = ROUNDUP(DataSize, PAGESIZE);
|
||||
|
||||
Status = NtAllocateVirtualMemory (
|
||||
NtCurrentProcess (),
|
||||
(PVOID*)&Param,
|
||||
0,
|
||||
&RegionSize,
|
||||
MEM_COMMIT,
|
||||
PAGE_READWRITE);
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
RtlReleasePebLock ();
|
||||
return Status;
|
||||
}
|
||||
|
||||
DPRINT ("Ppb allocated\n");
|
||||
|
||||
Param->TotalSize = RegionSize;
|
||||
Param->DataSize = DataSize;
|
||||
Param->Flags = TRUE;
|
||||
Param->Environment = Environment;
|
||||
// Param->Unknown1 =
|
||||
// Param->Unknown2 =
|
||||
// Param->Unknown3 =
|
||||
// Param->Unknown4 =
|
||||
|
||||
/* copy current directory */
|
||||
Dest = (PWCHAR)(((PBYTE)Param) +
|
||||
sizeof(RTL_USER_PROCESS_PARAMETERS));
|
||||
|
||||
Param->CurrentDirectory.DosPath.Buffer = Dest;
|
||||
Param->CurrentDirectory.DosPath.MaximumLength = MAX_PATH * sizeof(WCHAR);
|
||||
if (CurrentDirectory != NULL)
|
||||
{
|
||||
Param->CurrentDirectory.DosPath.Length = CurrentDirectory->Length;
|
||||
memcpy(Dest,
|
||||
CurrentDirectory->Buffer,
|
||||
CurrentDirectory->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + CurrentDirectory->Length);
|
||||
}
|
||||
|
||||
Dest = (PWCHAR)(((PBYTE)Param) + sizeof(RTL_USER_PROCESS_PARAMETERS) +
|
||||
/* (256 * sizeof(WCHAR)) + */ (MAX_PATH * sizeof(WCHAR)));
|
||||
|
||||
/* copy library path */
|
||||
Param->LibraryPath.Buffer = Dest;
|
||||
if (LibraryPath != NULL)
|
||||
{
|
||||
Param->LibraryPath.Length = LibraryPath->Length;
|
||||
memcpy (Dest,
|
||||
LibraryPath->Buffer,
|
||||
LibraryPath->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + LibraryPath->Length);
|
||||
}
|
||||
Param->LibraryPath.MaximumLength = Param->LibraryPath.Length +
|
||||
sizeof(WCHAR);
|
||||
*Dest = 0;
|
||||
Dest++;
|
||||
|
||||
/* copy command line */
|
||||
Param->CommandLine.Buffer = Dest;
|
||||
if (CommandLine != NULL)
|
||||
{
|
||||
Param->CommandLine.Length = CommandLine->Length;
|
||||
memcpy (Dest,
|
||||
CommandLine->Buffer,
|
||||
CommandLine->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + CommandLine->Length);
|
||||
}
|
||||
Param->CommandLine.MaximumLength = Param->CommandLine.Length + sizeof(WCHAR);
|
||||
*Dest = 0;
|
||||
Dest++;
|
||||
|
||||
/* copy image name */
|
||||
Param->ImageName.Buffer = Dest;
|
||||
if (ImageName != NULL)
|
||||
{
|
||||
Param->ImageName.Length = ImageName->Length;
|
||||
memcpy (Dest,
|
||||
ImageName->Buffer,
|
||||
ImageName->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + ImageName->Length);
|
||||
}
|
||||
Param->ImageName.MaximumLength = Param->ImageName.Length + sizeof(WCHAR);
|
||||
*Dest = 0;
|
||||
Dest++;
|
||||
|
||||
/* copy title */
|
||||
Param->Title.Buffer = Dest;
|
||||
if (Title != NULL)
|
||||
{
|
||||
Param->Title.Length = Title->Length;
|
||||
memcpy (Dest,
|
||||
Title->Buffer,
|
||||
Title->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + Title->Length);
|
||||
}
|
||||
Param->Title.MaximumLength = Param->Title.Length + sizeof(WCHAR);
|
||||
*Dest = 0;
|
||||
Dest++;
|
||||
|
||||
/* copy desktop */
|
||||
Param->Desktop.Buffer = Dest;
|
||||
if (Desktop != NULL)
|
||||
{
|
||||
Param->Desktop.Length = Desktop->Length;
|
||||
memcpy (Dest,
|
||||
Desktop->Buffer,
|
||||
Desktop->Length);
|
||||
Dest = (PWCHAR)(((PBYTE)Dest) + Desktop->Length);
|
||||
}
|
||||
Param->Desktop.MaximumLength = Param->Desktop.Length + sizeof(WCHAR);
|
||||
*Dest = 0;
|
||||
Dest++;
|
||||
|
||||
RtlDeNormalizeProcessParams (Param);
|
||||
*Ppb = Param;
|
||||
RtlReleasePebLock ();
|
||||
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $Id: create.c,v 1.9 2000/02/13 16:05:18 dwelch Exp $
|
||||
/* $Id: create.c,v 1.10 2000/02/14 14:13:33 dwelch Exp $
|
||||
*
|
||||
* COPYRIGHT: See COPYING in the top level directory
|
||||
* PROJECT: ReactOS kernel
|
||||
|
@ -266,12 +266,9 @@ static VOID PiTimeoutThread(struct _KDPC *dpc,
|
|||
|
||||
VOID PiBeforeBeginThread(VOID)
|
||||
{
|
||||
PPEB Peb;
|
||||
|
||||
DPRINT("PiBeforeBeginThread()\n");
|
||||
//KeReleaseSpinLock(&PiThreadListLock, PASSIVE_LEVEL);
|
||||
KeLowerIrql(PASSIVE_LEVEL);
|
||||
Peb = (PPEB)PEB_BASE;
|
||||
}
|
||||
|
||||
VOID PsBeginThread(PKSTART_ROUTINE StartRoutine, PVOID StartContext)
|
||||
|
@ -370,7 +367,10 @@ NTSTATUS PsInitializeThread(HANDLE ProcessHandle,
|
|||
Thread->ThreadsProcess = Process;
|
||||
KeInitializeDpc(&Thread->Tcb.TimerDpc, PiTimeoutThread, Thread);
|
||||
Thread->Tcb.WaitBlockList = NULL;
|
||||
InsertTailList( &Thread->ThreadsProcess->Pcb.ThreadListHead, &Thread->Tcb.ProcessThreadListEntry );
|
||||
InsertTailList(&Thread->ThreadsProcess->Pcb.ThreadListHead,
|
||||
&Thread->Tcb.ProcessThreadListEntry );
|
||||
DPRINT1("Inserting %x into process %x list\n",
|
||||
Thread, Thread->ThreadsProcess);
|
||||
KeInitializeDispatcherHeader(&Thread->Tcb.DispatcherHeader,
|
||||
InternalThreadType,
|
||||
sizeof(ETHREAD),
|
||||
|
|
|
@ -33,7 +33,10 @@ VOID PiTerminateProcessThreads(PEPROCESS Process, NTSTATUS ExitStatus)
|
|||
KIRQL oldlvl;
|
||||
PLIST_ENTRY current_entry;
|
||||
PETHREAD current;
|
||||
|
||||
|
||||
DPRINT("PiTerminateProcessThreads(Process %x, ExitStatus %x)\n",
|
||||
Process, ExitStatus);
|
||||
|
||||
KeAcquireSpinLock(&PiThreadListLock, &oldlvl);
|
||||
|
||||
current_entry = PiThreadListHead.Flink;
|
||||
|
@ -44,6 +47,7 @@ VOID PiTerminateProcessThreads(PEPROCESS Process, NTSTATUS ExitStatus)
|
|||
current != PsGetCurrentThread())
|
||||
{
|
||||
KeReleaseSpinLock(&PiThreadListLock, oldlvl);
|
||||
DPRINT("Terminating %x\n", current);
|
||||
PsTerminateOtherThread(current, ExitStatus);
|
||||
KeAcquireSpinLock(&PiThreadListLock, &oldlvl);
|
||||
current_entry = PiThreadListHead.Flink;
|
||||
|
@ -52,6 +56,7 @@ VOID PiTerminateProcessThreads(PEPROCESS Process, NTSTATUS ExitStatus)
|
|||
}
|
||||
|
||||
KeReleaseSpinLock(&PiThreadListLock, oldlvl);
|
||||
DPRINT("Finished PiTerminateProcessThreads()\n");
|
||||
}
|
||||
|
||||
VOID PsReapThreads(VOID)
|
||||
|
@ -90,7 +95,7 @@ VOID PsReapThreads(VOID)
|
|||
KeReleaseSpinLock(&PiThreadListLock, oldIrql);
|
||||
ObDereferenceObject(current);
|
||||
KeAcquireSpinLock(&PiThreadListLock, &oldIrql);
|
||||
if(IsListEmpty(&Process->Pcb.ThreadListHead))
|
||||
if (IsListEmpty(&Process->Pcb.ThreadListHead))
|
||||
{
|
||||
/*
|
||||
* TODO: Optimize this so it doesnt jerk the IRQL around so
|
||||
|
@ -123,17 +128,10 @@ VOID PsTerminateCurrentThread(NTSTATUS ExitStatus)
|
|||
KeAcquireSpinLock(&PiThreadListLock, &oldIrql);
|
||||
|
||||
CurrentThread->ExitStatus = ExitStatus;
|
||||
|
||||
DPRINT("ObGetReferenceCount(CurrentThread) %d\n",
|
||||
ObGetReferenceCount(CurrentThread));
|
||||
DPRINT("ObGetHandleCount(CurrentThread) %x\n",
|
||||
ObGetHandleCount(CurrentThread));
|
||||
|
||||
KeAcquireDispatcherDatabaseLock(FALSE);
|
||||
CurrentThread->Tcb.DispatcherHeader.SignalState = TRUE;
|
||||
KeDispatcherObjectWake(&CurrentThread->Tcb.DispatcherHeader);
|
||||
|
||||
DPRINT("Type %x\n",
|
||||
BODY_TO_HEADER(CurrentThread->ThreadsProcess)->ObjectType);
|
||||
KeReleaseDispatcherDatabaseLock(FALSE);
|
||||
|
||||
PsDispatchThreadNoLock(THREAD_STATE_TERMINATED_1);
|
||||
KeBugCheck(0);
|
||||
|
@ -146,18 +144,24 @@ VOID PsTerminateOtherThread(PETHREAD Thread, NTSTATUS ExitStatus)
|
|||
{
|
||||
KIRQL oldIrql;
|
||||
|
||||
DPRINT("PsTerminateOtherThread(Thread %x, ExitStatus %x)\n",
|
||||
Thread, ExitStatus);
|
||||
|
||||
KeAcquireSpinLock(&PiThreadListLock, &oldIrql);
|
||||
if (Thread->Tcb.State == THREAD_STATE_RUNNABLE)
|
||||
{
|
||||
DPRINT("Removing from runnable queue\n");
|
||||
RemoveEntryList(&Thread->Tcb.QueueListEntry);
|
||||
}
|
||||
DPRINT("Removing from process queue\n");
|
||||
RemoveEntryList(&Thread->Tcb.ProcessThreadListEntry);
|
||||
Thread->Tcb.State = THREAD_STATE_TERMINATED_2;
|
||||
Thread->Tcb.DispatcherHeader.SignalState = TRUE;
|
||||
KeDispatcherObjectWake(&Thread->Tcb.DispatcherHeader);
|
||||
KeReleaseSpinLock(&PiThreadListLock, oldIrql);
|
||||
KeReleaseSpinLock(&PiThreadListLock, oldIrql);
|
||||
if (IsListEmpty(&Thread->ThreadsProcess->Pcb.ThreadListHead))
|
||||
{
|
||||
DPRINT("Terminating associated process\n");
|
||||
PiTerminateProcess(Thread->ThreadsProcess, ExitStatus);
|
||||
}
|
||||
ObDereferenceObject(Thread);
|
||||
|
@ -166,10 +170,8 @@ VOID PsTerminateOtherThread(PETHREAD Thread, NTSTATUS ExitStatus)
|
|||
NTSTATUS STDCALL PiTerminateProcess(PEPROCESS Process,
|
||||
NTSTATUS ExitStatus)
|
||||
{
|
||||
KIRQL oldlvl;
|
||||
|
||||
DPRINT("PsTerminateProcess(Process %x, ExitStatus %x)\n",
|
||||
Process, ExitStatus);
|
||||
DPRINT("PiTerminateProcess(Process %x, ExitStatus %x)\n",
|
||||
Process, ExitStatus);
|
||||
|
||||
if (Process->Pcb.ProcessState == PROCESS_STATE_TERMINATED)
|
||||
{
|
||||
|
@ -178,13 +180,11 @@ NTSTATUS STDCALL PiTerminateProcess(PEPROCESS Process,
|
|||
|
||||
PiTerminateProcessThreads(Process, ExitStatus);
|
||||
ObCloseAllHandles(Process);
|
||||
KeRaiseIrql(DISPATCH_LEVEL, &oldlvl);
|
||||
KeAcquireDispatcherDatabaseLock(FALSE);
|
||||
Process->Pcb.ProcessState = PROCESS_STATE_TERMINATED;
|
||||
Process->Pcb.DispatcherHeader.SignalState = TRUE;
|
||||
DPRINT("Type %x\n", BODY_TO_HEADER(Process)->ObjectType);
|
||||
KeDispatcherObjectWake(&Process->Pcb.DispatcherHeader);
|
||||
KeLowerIrql(oldlvl);
|
||||
DPRINT("Type %x\n", BODY_TO_HEADER(Process)->ObjectType);
|
||||
KeReleaseDispatcherDatabaseLock(FALSE);
|
||||
return(STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ NTSTATUS STDCALL NtTerminateProcess(IN HANDLE ProcessHandle,
|
|||
PEPROCESS Process;
|
||||
|
||||
DPRINT("NtTerminateProcess(ProcessHandle %x, ExitStatus %x)\n",
|
||||
ProcessHandle, ExitStatus);
|
||||
ProcessHandle, ExitStatus);
|
||||
|
||||
Status = ObReferenceObjectByHandle(ProcessHandle,
|
||||
PROCESS_TERMINATE,
|
||||
|
@ -203,7 +203,7 @@ NTSTATUS STDCALL NtTerminateProcess(IN HANDLE ProcessHandle,
|
|||
UserMode,
|
||||
(PVOID*)&Process,
|
||||
NULL);
|
||||
if (Status != STATUS_SUCCESS)
|
||||
if (!NT_SUCCESS(Status))
|
||||
{
|
||||
return(Status);
|
||||
}
|
||||
|
@ -211,9 +211,7 @@ NTSTATUS STDCALL NtTerminateProcess(IN HANDLE ProcessHandle,
|
|||
PiTerminateProcess(Process, ExitStatus);
|
||||
if (PsGetCurrentThread()->ThreadsProcess == Process)
|
||||
{
|
||||
DPRINT("Type %x\n", BODY_TO_HEADER(Process)->ObjectType);
|
||||
ObDereferenceObject(Process);
|
||||
DPRINT("Type %x\n", BODY_TO_HEADER(Process)->ObjectType);
|
||||
PsTerminateCurrentThread(ExitStatus);
|
||||
}
|
||||
ObDereferenceObject(Process);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $Id: smss.c,v 1.5 2000/01/26 10:07:30 dwelch Exp $
|
||||
/* $Id: smss.c,v 1.6 2000/02/14 14:13:34 dwelch Exp $
|
||||
*
|
||||
* smss.c - Session Manager
|
||||
*
|
||||
|
@ -125,7 +125,7 @@ void NtProcessStartup (PPEB Peb)
|
|||
...);
|
||||
#endif
|
||||
|
||||
NtTerminateProcess( NtCurrentProcess(), 0 );
|
||||
NtTerminateProcess(NtCurrentProcess(), 0);
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
|
|
Loading…
Reference in a new issue